Переклад поста 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, що викликало спрацювання оператора ||.
