По сути функция в bash
является обычной переменной, но с более широкими возможностями.
Основное применение – в тех случаях, когда один и тот же код необходимо использовать несколько раз и/или в разных связанных скриптах.
Содержание
Объявление и вызов функции
Объявляется функция так:
function function_name () { function body }
Или:
function one { echo "One" } two () { echo "Two" } function three () { echo "Three" }
Однако, наиболее правильным вариантом, в целях совместимости скрипта с разными shell
-оболочками будет второй:
two () { echo "Two" }
И старайтесь никогда не использовать третий вариант:
function three () { echo "Three" }
Вызвать функцию можно просто указав её имя в теле скрипта:
#!/bin/bash function one { echo "One" } one
[simterm]
$ ./example.sh One
[/simterm]
Важно, что бы объявление функции было выполнено до того, как она будет вызвана, иначе будет получена ошибка:
#!/bin/bash function one { echo "One" } one two function two { echo "Two" }
[simterm]
$ ./example.sh One ./example.sh: line 7: two: command not found
[/simterm]
Вызов функции с аргументами
Перейдём к более сложной функции, и рассмотрим вызов функции с аргументами.
Для примера – возьмём функцию, которая вызывается в том месте кода, где необходимо получить ответ от пользователя:
#!/bin/bash answer () { while read response; do echo case $response in [yY][eE][sS]|[yY]) printf "$1" $2 break ;; [nN][oO]|[nN]) printf "$3" $4 break ;; *) printf "Please, enter Y(yes) or N(no)! " esac done } echo "Run application? (Yes/No) " answer "Run" "" "Not run" ""
В данном случае, функция answer()
ожидает ответа от пользователя в стиле Yes
или No
(или любая вариация, заданная в выражении [yY][eE][sS]|[yY]
или [nN][oO]|[nN]
), и в зависимости от ответа выполняет определённое действие.
В случае ответа Yes
– будет выполнено действие, заданное в первом аргументе $1
, с которым была вызвана функция.
Проверим:
[simterm]
$ bash test.sh Run application? (Yes/No) y Run
[/simterm]
С ответом No:
[simterm]
$ ./example.sh Run application? (Yes/No) no Not run
[/simterm]
Вызов команд непосредственно из аргументов, а тем более из переменных, считается не самым хорошим решением, поэтому давайте перепишем её и вызовем с операторами && (в случае успешного выполнения, то есть при получении кода 0) и || – в случае ошибки и получения кода ответа 1:
#!/bin/bash answer () { while read response; do echo case $response in [yY][eE][sS]|[yY]) printf "$1\n" return 0 break ;; [nN][oO]|[nN]) printf "$2\n" return 1 break ;; *) printf "Please, enter Y(yes) or N(no)! " esac done } echo -e "\nRun application? (Yes/No) " answer "Run" "Will not run" && echo "I'm script" || echo "Doing nothing"
Теперь мы первым аргументом передаём функции ответ “Run
“, и в случае ответа пользователя Yes
– выполняем printf "Run"
и echo "I'm script"
. Если выбран ответ No
– то мы печатаем второй аргумент Will not run
и выполняем действие echo "Doing nothing"
:
[simterm]
$ bash test.sh Run application? (Yes/No) y Run I'm script $ bash test.sh Run application? (Yes/No) no Will not run Doing nothing
[/simterm]
Соответственно, вместо echo
можно выполнить любую другую команду:
#!/bin/bash answer () { while read response; do echo case $response in [yY][eE][sS]|[yY]) printf "$1\n" return 0 break ;; [nN][oO]|[nN]) printf "$2\n" return 1 break ;; *) printf "Please, enter Y(yes) or N(no)! " esac done } echo -e "\nKill TOP application? (Yes/No) " answer "Killing TOP" "Left it alive" && pkill top || echo "Doing nothing"
[simterm]
$ ./example.sh Kill TOP application? (Yes/No) y Killing TOP
[/simterm]
Важно учитывать, что в случае если первая команда завершится неудачно (в данном примере – pkill
не найдёт указанный процесс) – то функция вернёт код 1, и будет выполнена вторая часть:
[simterm]
$ ./example.sh Kill TOP application? (Yes/No) y Killing TOP Doing nothing
Переменные в функциях
В аргументах так же можно использовать переменные.
Например, можно определить несколько вариантов ответов в разных переменных, и использовать нужную в разных случаях:
#!/bin/bash answer () { while read response; do echo case $response in [yY][eE][sS]|[yY]) printf "$1\n" return 0 break ;; [nN][oO]|[nN]) printf "$2\n" return 1 break ;; *) printf "Please, enter Y(yes) or N(no)! " esac done } replay1="Killing TOP" replay2="Left it alive" echo -e "\nKill TOP application? (Yes/No) " answer "$replay1" "$replay2" && echo "I'm script" || echo "Doing nothing"
[simterm]
$ ./example.sh Kill TOP application? (Yes/No) y Killing TOP I'm script
$ ./example.sh Kill TOP application? (Yes/No) n Left it alive Doing nothing
[/simterm]
Как и с обычными переменными – функции используют “позиционные агрументы”, т.е.:
$#
– вывод количества переданных аргументов;$*
– вывод списка всех переданных аргументов;$@
– то же, что и$*
– но каждый аргумент считается как простое слово (строка);$1 - $9
– нумерованные аргументы, в зависимости от позиции в списке.
Например – создадим такой скрипт с функцией, которая должна вывести количество переданных аргументов:
#!/bin/bash example () { echo $# shift } example $*
[simterm]
$ ./example.sh 1 2 3 4 4
[/simterm]
Или – просто вывести на экран все переданные ей аргументы:
#!/bin/bash example () { echo $* shift } example $*
[simterm]
$ ./example.sh 1 2 3 4 1 2 3 4
[/simterm]
А можно аргументы передавать прямо при вызове функции, а не при вызове скрипта как в примере выше:
#!/bin/bash example () { echo $* shift } example 1 2 3 4
[simterm]
$ ./example.sh 1 2 3 4
[/simterm]
Локальные переменные
По-умолчанию, все заданные переменные в bash
-скриптах считаются глобальными в рамках самого скрипта, но в функции можно объявить переменную, которая будет доступна только во время её (функции) выполнения.
Пример:
#!/bin/bash ex0=0 example () { local ex1=1 echo "$ex1" } example [[ $ex0 ]] && echo "Variable found" || echo "Can't find variable!" [[ $ex1 ]] && echo "Variable found" || echo "Can't find variable!"
Проверяем:
[simterm]
$ bash test.sh 1 Variable found Can't find variable!
[/simterm]
Математические операции в функциях
Как и в переменных, в функциях допустимо использование математических операций.
К примеру такая функция:
#!/bin/bash mat () { a=1 (( a++ )) echo $a } mat
В результате получаем значение переменной $a
+ единица:
[simterm]
$ ./mat.sh 2
[/simterm]
Более сложный вариант – с использованием нескольких переменных и вычислением их значения:
#!/bin/bash mat () { a=1 b=2 c=$(( a + b )) echo $c } mat
Результат:
[simterm]
$ ./mat.sh 3
[/simterm]
Ещё вариант – с использованием аргументов:
#!/bin/bash mat () { a=$1 b=$2 c=$(( a + b )) echo $c } mat $1 $2
Выполняем:
[simterm]
$ ./mat.sh 1 1 2
[/simterm]
Рекурсивные функции
Рекурсивная функция – функция, которая при вызове вызывает сама себя.
Например:
#!/bin/bash recursion () { count=$(( $count + 1 )) echo $count recursion } recursion
Такая функция будет бесконечно вызывать сама себя, пока её выполнение не будет прервано вручную:
[simterm]
$ ./example.sh ... 913 914 915
[/simterm]
Для большей наглядности добавим цикл, который проверяет условие – если переменная $count
превысит значение переменной $recursions
– функция остановит своё выполнение:
#!/bin/bash count=0 recursions=4 recursion () { count=$(( $count + 1 )) echo $count while [ $count -le $recursions ]; do recursion done } recursion
Выполнение:
[simterm]
$ ./example.sh 1 2 3 4 5
[/simterm]
Для упрощения скрипта – можно заменить выражение count=$(( $count + 1 ))
на (( count++ ))
:
#!/bin/bash count=0 recursions=4 recursion () { (( count++ )) echo $count while [ $count -le $recursions ]; do recursion done } recursion
Проверяем:
[simterm]
$ ./example.sh 1 2 3 4 5
[/simterm]
Экспорт функций
Что бы передать функцию в следующий скрипт, вызываемом в новом (дочернем) экземпляре shell
– её необходимо экспортировать.
Для примера возьмём два файла – в файле 1.sh
мы объявим функцию и вызове скрипт 2.sh
:
#!/bin/bash one () { echo "one" } bash 2.sh
А в файле 2.sh
– попробуем эту функцию вызвать:
#!/bin/bash one
Проверяем:
[simterm]
$ ./1.sh 2.sh: line 3: one: command not found
[/simterm]
Теперь – экспортируем функцию с помощью опции export
и ключа -f
:
#!/bin/bash one () { echo "one" } export -f one bash 2.sh
Выполняем:
[simterm]
$ ./1.sh one
[/simterm]
Другой вариант – вызывать следующий скрипт в том же экземпляре шела:
#!/bin/bash one () { echo "one" } source 2.sh
Или так:
#!/bin/bash one () { echo "one" } . 2.sh
Оба варианта равнозначны и дадут один результат:
[simterm]
$ ./1.sh one
[/simterm]
Проверка наличия функций
Иногда необходимо проверить имеется ли функция, перед её выполнением. Для этого удобно использовать команду declare
.
Вызванная с ключём -f
и без аргументов declare
выведет описание всех имеющихся функций:
#!/bin/bash one () { echo "one" } two () { echo "two" } declare -f
Результат:
[simterm]
$ ./test.sh one () { echo "one" } two () { echo "two" }
[/simterm]
С ключём -F
– только названия:
#!/bin/bash one () { echo "one" } two () { echo "two" } declare -F
И:
[simterm]
$ ./test.sh declare -f one declare -f two
[/simterm]
Если задать имена функций в качестве аргументов – declare
просто выведет их мена:
#!/bin/bash one () { echo "one" } two () { echo "two" } declare -F one two
Проверяем:
[simterm]
$ ./test.sh one two
[/simterm]
Можно задать ключ -f
и имя функции, тогда будет выведено только тело указанной функции:
#!/bin/bash one () { echo "one" } two () { echo "two" } declare -f one
Запускаем:
[simterm]
$ ./test.sh one () { echo "one" }
[/simterm]
Проверить наличие функций перед их выполнением можно с помощью дополнительной функции, которой передаются имена проверяемых функций:
#!/bin/bash one () { echo "one" } two () { echo "two" } isDefined() { declare -f "$@" > /dev/null && echo "Functions exist" || echo "There is no some functions!" } isDefined one two
Обратите внимание на использование “$@
” – как писалось выше, именно такой параметр выводит аргумент “как есть”, без каких-либо интерпретаций bash
-ем.
Запустим скрипт для проверки:
[simterm]
$ ./test.sh Functions exist
[/simterm]
А теперь – попробуем добавить одну “лишнюю” функцию:
#!/bin/bash one () { echo "one" } two () { echo "two" } isDefined() { declare -f "$@" > /dev/null && echo "Functions exist" || echo "There is no some functions!" } isDefined one two three
Результат:
[simterm]
$ ./test.sh There is no some functions!
[/simterm]
declare
обнаружил отсутствие функции с именем three
и вернул код 1, что вызвало срабатывание оператора ||
.