По сути функция в 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, что вызвало срабатывание оператора ||.