Переклад поста 2013 року з деякими правками, але все ще актуальний для вивчення BASH.
По суті функція в 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]
А тепер – спробуємо додати одну “зайву” функцію до виклику isDefined()
:
#!/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, що викликало спрацювання оператора ||
.