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