22.1. Сложные функции и сложности с функциями


Функции могут принимать входные аргументы и возвращать код завершения.

  1. function_name $arg1 $arg2

Доступ к входным аргументам, в функциях, производится посредством позиционных параметров, т.е. $1, $2 и так далее.

Пример 22-2. Функция с аргументами

  1. #!/bin/bash
  2. # Функции и аргументы
  3. DEFAULT=default                             # Значение аргумента по-умолчанию.
  4. func2 () {
  5.    if [ -z "$1" ]                           # Длина аргумента #1 равна нулю?
  6.    then
  7.      echo "-Аргумент #1 имеет нулевую длину.-"  # Или аргумент не был передан функции.
  8.    else
  9.      echo "-Аргумент #1: \"$1\".-"
  10.    fi
  11.    variable=${1-$DEFAULT}                   #  Что делает
  12.    echo "variable = $variable"              #+ показанная подстановка параметра?
  13.                                             #  ---------------------------
  14.                                             #  Она различает отсутствующий аргумент
  15.                                             #+ от "пустого" аргумента.
  16.    if [ "$2" ]
  17.    then
  18.      echo "-Аргумент #2: \"$2\".-"
  19.    fi
  20.    return 0
  21. }
  22. echo
  23. echo "Вызов функции без аргументов."
  24. func2
  25. echo
  26. echo "Вызов функции с \"пустым\" аргументом."
  27. func2 ""
  28. echo
  29. echo "Вызов функции с неинициализированным аргументом."
  30. func2 "$uninitialized_param"
  31. echo
  32. echo "Вызов функции с одним аргументом."
  33. func2 first
  34. echo
  35. echo "Вызов функции с двумя аргументами."
  36. func2 first second
  37. echo
  38. echo "Вызов функции с аргументами \"\" \"second\"."
  39. func2 "" second       # Первый параметр "пустой"
  40. echo                  # и второй параметр — ASCII-строка.
  41. exit 0
Important

Команда shift вполне применима и к аргументам функций (см. Пример 33-11).

В отличие от других языков программирования, в сценариях на языке командной оболочке, аргументы в функции передаются по значению. Переменные (которые фактически являются указателями) при передаче в функции в виде параметров, интерпретируются как строковые литералы. Функции всегда интерпретируют свои аргументы буквально.

Механизм косвенных ссылок на переменные (см. Пример 34-2) слишком неудобен для передачи аргументов по ссылке.

Пример 22-3. Передача косвенных ссылок в функцию

  1. #!/bin/bash
  2. # ind-func.sh: Передача косвенных ссылок в функцию.
  3. echo_var ()
  4. {
  5. echo "$1"
  6. }
  7. message=Hello
  8. Hello=Goodbye
  9. echo_var "$message"        # Hello
  10. # А теперь передадим функции косвенную ссылку.
  11. echo_var "${!message}"     # Goodbye
  12. echo "-------------"
  13. # Что произойдет, если изменить содержимое переменной "Hello"?
  14. Hello="Hello, again!"
  15. echo_var "$message"        # Hello
  16. echo_var "${!message}"     # Hello, again!
  17. exit 0

Тут же возникает вопрос: "Возможно ли изменить значение переменной, которая была передана по ссылке?"

Пример 22-4. Изменение значения переменной, переданной в функцию по ссылке.

  1. #!/bin/bash
  2. # dereference.sh
  3. # Изменение значения переменной, переданной в функцию по ссылке.
  4. # Автор: Bruce W. Clare.
  5. dereference ()
  6. {
  7.      y=\$"$1"   # Имя переменной.
  8.      echo $y    # $Junk
  9.      x=`eval "expr \"$y\" "`
  10.      echo $1=$x
  11.      eval "$1=\"Некий другой текст \""  # Присвоить новое значение.
  12. }
  13. Junk="Некий текст"
  14. echo $Junk "до того как..."    # Некий текст до того как...
  15. dereference Junk
  16. echo $Junk "после того как..." # Некий другой текст после того как...
  17. exit 0

Пример 22-5. Еще один пример разыменования параметров функции, передаваемых по ссылке.

  1. #!/bin/bash
  2. ITERATIONS=3  # Количество вводимых значений.
  3. icount=1
  4. my_read () {
  5.   # При вызове my_read varname,
  6.   # выводит предыдущее значение в квадратных скобках,
  7.   # затем просит ввести новое значение.
  8.   local local_var
  9.   echo -n "Введите говое значение переменной "
  10.   eval 'echo -n "[$'$1'] "'  # Прежнее значение.
  11.   read local_var
  12.   [ -n "$local_var" ] && eval $1=\$local_var
  13.   # Последовательность "And-list": если "local_var" не пуста, то ее значение переписывается в "$1".
  14. }
  15. echo
  16. while [ "$icount" -le "$ITERATIONS" ]
  17. do
  18.   my_read var
  19.   echo "Значение #$icount = $var"
  20.   let "icount += 1"
  21.   echo
  22. done
  23. # Спасибо Stephane Chazelas за этот поучительный пример.
  24. exit 0

Exit и Return

код завершения

Функции возвращают значение в виде кода завершения. Код завершения может быть задан явно, с помощью команды return, в противном случае будет возвращен код завершения последней команды в функции (0 — в случае успеха, иначе — ненулевой код ошибки). Код завершения в сценарии может быть получен через переменную $?.

return

Завершает исполнение функции. Команда return [51] может иметь необязательный аргумент типа integer, который возвращается в вызывающий сценарий как "код завершения" функции, это значение так же записывается в переменную $?.

Пример 22-6. Наибольшее из двух чисел

  1. #!/bin/bash
  2. # max.sh: Наибольшее из двух целых чисел.
  3. E_PARAM_ERR=-198    # Если функции передано меньше двух параметров.
  4. EQUAL=-199          # Возвращаемое значение, если числа равны.
  5. max2 ()             # Возвращает наибольшее из двух чисел.
  6. {                   # Внимание: сравниваемые числа должны быть меньше 257.
  7. if [ -z "$2" ]
  8. then
  9.   return $E_PARAM_ERR
  10. fi
  11. if [ "$1" -eq "$2" ]
  12. then
  13.   return $EQUAL
  14. else
  15.   if [ "$1" -gt "$2" ]
  16.   then
  17.     return $1
  18.   else
  19.     return $2
  20.   fi
  21. fi
  22. }
  23. max2 33 34
  24. return_val=$?
  25. if [ "$return_val" -eq $E_PARAM_ERR ]
  26. then
  27.   echo "Функции должно быть передано два аргумента."
  28. elif [ "$return_val" -eq $EQUAL ]
  29.   then
  30.     echo "Числа равны."
  31. else
  32.     echo "Наибольшее из двух чисел: $return_val."
  33. fi
  34. exit 0
  35. #  Упражнение:
  36. #  ---------------
  37. #  Сделайте этот сценарий интерактивным,
  38. #+ т.е. заставьте сценарий запрашивать числа для сравнения у пользователя (два числа).
Tip

Для случаев, когда функция должна возвращать строку или массив, используйте специальные переменные.

  1. count_lines_in_etc_passwd()
  2. {
  3.   [[ -r /etc/passwd ]] && REPLY=$(echo $(wc -l < /etc/passwd))
  4.   # Если файл /etc/passwd доступен на чтение, то в переменную REPLY заносится число строк.
  5.   # Возвращаются как количество строк, так и код завершения.
  6.   # Команда 'echo' может показаться ненужной, но . . .
  7.   #+ она предотвращает вывод лишних пробелов.
  8. }
  9. if count_lines_in_etc_passwd
  10. then
  11.   echo "В файле /etc/passwd найдено $REPLY строк."
  12. else
  13.   echo "Невозможно подсчитать число строк в файле /etc/passwd."
  14. fi
  15. # Спасибо S.C.


Пример 22-7. Преобразование чисел в римскую форму записи

  1. #!/bin/bash
  2. # Преобразование чисел из арабской формы записи в римскую
  3. # Диапазон: 0 - 200
  4. # Расширение диапазона представляемых чисел и улучшение сценария
  5. # оставляю вам, в качестве упражнения.
  6. # Порядок использования: roman number-to-convert
  7. LIMIT=200
  8. E_ARG_ERR=65
  9. E_OUT_OF_RANGE=66
  10. if [ -z "$1" ]
  11. then
  12.   echo "Порядок использования: `basename $0` number-to-convert"
  13.   exit $E_ARG_ERR
  14. fi
  15. num=$1
  16. if [ "$num" -gt $LIMIT ]
  17. then
  18.   echo "Выход за границы диапазона!"
  19.   exit $E_OUT_OF_RANGE
  20. fi
  21. to_roman ()   # Функция должна быть объявлена до того как она будет вызвана.
  22. {
  23. number=$1
  24. factor=$2
  25. rchar=$3
  26. let "remainder = number - factor"
  27. while [ "$remainder" -ge 0 ]
  28. do
  29.   echo -n $rchar
  30.   let "number -= factor"
  31.   let "remainder = number - factor"
  32. done
  33. return $number
  34.        # Упражнение:
  35.        # --------
  36.        # Объясните — как работает функция.
  37.        # Подсказка: деление последовательным вычитанием.
  38. }
  39. to_roman $num 100 C
  40. num=$?
  41. to_roman $num 90 LXXXX
  42. num=$?
  43. to_roman $num 50 L
  44. num=$?
  45. to_roman $num 40 XL
  46. num=$?
  47. to_roman $num 10 X
  48. num=$?
  49. to_roman $num 9 IX
  50. num=$?
  51. to_roman $num 5 V
  52. num=$?
  53. to_roman $num 4 IV
  54. num=$?
  55. to_roman $num 1 I
  56. echo
  57. exit 0

См. также Пример 10-28.

Important

Наибольшее положительное целое число, которое может вернуть функция — 255. Команда return очень тесно связана с понятием код завершения, что объясняет это специфическое ограничение. К счастью существуют различные способы преодоления этого ограничения.

Пример 22-8. Проверка возможности возврата функциями больших значений

  1. #!/bin/bash
  2. # return-test.sh
  3. # Наибольшее целое число, которое может вернуть функция, не может превышать 256.
  4. return_test ()         # Просто возвращает то, что ей передали.
  5. {
  6.   return $1
  7. }
  8. return_test 27         # o.k.
  9. echo $?                # Возвращено число 27.
  10. return_test 255        # o.k.
  11. echo $?                # Возвращено число 255.
  12. return_test 257        # Ошибка!
  13. echo $?                # Возвращено число 1.
  14. return_test -151896    # Как бы то ни было, но для больших отрицательных чисел проходит!
  15. echo $?                # Возвращено число -151896.
  16. exit 0

Самый простой способ вернуть из функции большое положительное число — это присвоить "возвращаемое значение" глобальной переменной.

  1. Return_Val=   # Глобальная переменная, которая хранит значение, возвращаемое функцией.
  2. alt_return_test ()
  3. {
  4.   fvar=$1
  5.   Return_Val=$fvar
  6.   return   # Возвратить 0 (успешное завершение).
  7. }
  8. alt_return_test 1
  9. echo $?                                  # 0
  10. echo "Функция вернула число $Return_Val" # 1
  11. alt_return_test 255
  12. echo "Функция вернула число $Return_Val" # 255
  13. alt_return_test 257
  14. echo "Функция вернула число $Return_Val" # 257
  15. alt_return_test 25701
  16. echo "Функция вернула число $Return_Val" #25701


Еще более элегантный способ заключается в передаче возвращаемого значания команде echo, для вывода на stdout, которое затем снимается со стандартного вывода конструкцией подстановки команд. См. обсуждение этого приема в Section 33.7.

Пример 22-9. Сравнение двух больших целых чисел

  1. #!/bin/bash
  2. # max2.sh: Наибольшее из двух БОЛЬШИХ целых чисел.
  3. # Это модификация предыдущего примера "max.sh",
  4. # которая позволяет выполнять сравнение больших целых чисел.
  5. EQUAL=0             # Если числа равны.
  6. MAXRETVAL=255       # Максимально возможное положительное число, которое может вернуть функция.
  7. E_PARAM_ERR=-99999  # Код ошибки в параметрах.
  8. E_NPARAM_ERR=99999  # "Нормализованный" код ошибки в параметрах.
  9. max2 ()             # Возвращает наибольшее из двух больших целых чисел.
  10. {
  11. if [ -z "$2" ]
  12. then
  13.   return $E_PARAM_ERR
  14. fi
  15. if [ "$1" -eq "$2" ]
  16. then
  17.   return $EQUAL
  18. else
  19.   if [ "$1" -gt "$2" ]
  20.   then
  21.     retval=$1
  22.   else
  23.     retval=$2
  24.   fi
  25. fi
  26. # -------------------------------------------------------------- #
  27. # Следующие строки позволяют "обойти" ограничение
  28. if [ "$retval" -gt "$MAXRETVAL" ]    # Если больше предельного значения,
  29. then                                 # то
  30.   let "retval = (( 0 - $retval ))"   # изменение знака числа.
  31.   # (( 0 - $VALUE )) изменяет знак числа.
  32. fi
  33. # Функции имеют возможность возвращать большие *отрицательные* числа.
  34. # -------------------------------------------------------------- #
  35. return $retval
  36. }
  37. max2 33001 33997
  38. return_val=$?
  39. # -------------------------------------------------------------------------- #
  40. if [ "$return_val" -lt 0 ]                  # Если число отрицательное,
  41. then                                        # то
  42.   let "return_val = (( 0 - $return_val ))"  # опять изменить его знак.
  43. fi                                          # "Абсолютное значение" переменной $return_val.
  44. # -------------------------------------------------------------------------- #
  45. if [ "$return_val" -eq "$E_NPARAM_ERR" ]
  46. then                   # Признак ошибки в параметрах, при выходе из функции так же поменял знак.
  47.   echo "Ошибка: Недостаточно аргументов."
  48. elif [ "$return_val" -eq "$EQUAL" ]
  49.   then
  50.     echo "Числа равны."
  51. else
  52.     echo "Наиболшее число: $return_val."
  53. fi
  54. exit 0

См. также Пример A-8.

Упражнение: Используя только что полученные знания, добавьте в предыдущий пример, преобразования чисел в римскую форму записи, возможность обрабатывать большие числа.

Перенаправление

Перенаправление ввода для функций

Функции — суть есть блок кода, а это означает, что устройство stdin для функций может быть переопределено (перенаправление stdin) (как в Пример 3-1).

Пример 22-10. Настоящее имя пользователя

  1. #!/bin/bash
  2. # По имени пользователя получить его "настоящее имя" из /etc/passwd.
  3. ARGCOUNT=1  # Ожидается один аргумент.
  4. E_WRONGARGS=65
  5. file=/etc/passwd
  6. pattern=$1
  7. if [ $# -ne "$ARGCOUNT" ]
  8. then
  9.   echo "Порядок использования: `basename $0` USERNAME"
  10.   exit $E_WRONGARGS
  11. fi
  12. file_excerpt ()  # Производит поиск в файле по заданному шаблону, выводит требуемую часть строки.
  13. {
  14. while read line
  15. do
  16.   echo "$line" | grep $1 | awk -F":" '{ print $5 }'  # Указывет awk использовать ":" как разделитель полей.
  17. done
  18. } <$file  # Подменить stdin для функции.
  19. file_excerpt $pattern
  20. # Да, этот сценарий можно уменьшить до
  21. #       grep PATTERN /etc/passwd | awk -F":" '{ print $5 }'
  22. # или
  23. #       awk -F: '/PATTERN/ {print $5}'
  24. # или
  25. #       awk -F: '($1 == "username") { print $5 }'
  26. # Однако, это было бы не так поучительно.
  27. exit 0

Ниже приводится альтернативный, и возможно менее запутанный, способ перенаправления ввода для функций. Он заключается в использовании перенаправления ввода для блока кода, заключенного в фигурные скобки, в пределах функции.

  1. # Вместо:
  2. Function ()
  3. {
  4.  ...
  5.  } < file
  6. # Попробуйте так:
  7. Function ()
  8. {
  9.   {
  10.     ...
  11.    } < file
  12. }
  13. # Похожий вариант,
  14. Function ()  # Тоже работает.
  15. {
  16.   {
  17.    echo $*
  18.   } | tr a b
  19. }
  20. Function ()  # Этот вариант не работает.
  21. {
  22.   echo $*
  23. } | tr a b   # Наличие вложенного блока кода — обязательное условие.
  24. # Спасибо S.C.



[51]    Команда return — это встроенная команда Bash.