Багато скриптів Bash написано в суто імперативному стилі: виконати одну команду, потім виконати іншу, і так далі. Іноді потрібно обʼєднати послідовність команд, які за задумом виконують одну спільну мету. Саме тут у пригоді стають функції.
Функцію визначають так:
my_function () {
COMMANDS
}
Порожні дужки просто означають, що ми визначаємо функцію. Усередину них нічого не пишемо.
Визначені функції поводяться як будь-яка інша команда (вбудована чи ні).
Як і будь-якій команді, своїм функціям можна передавати аргументи.
Усередині функції ми звертаємося до аргументів через позиційні параметри, $1, $2 тощо.
(Пригадаймо, що позиційні параметри ми вивчали в концепції Змінні.)
Спеціальний параметр $0 усередині функції не змінюється: це досі назва скрипта, який виконується.
Функція, яка виконується зараз, може дізнатися своє імʼя зі змінної $FUNCNAME.
Про це йдеться в розділі 3.4.2 Спеціальні параметри посібника.
Усередині функції можна визначати змінні.
Якщо оголосити змінні командою local, область видимості змінної обмежується поточною функцією (і будь-якими функціями, які вона викликає).
Інакше змінна потрапляє в глобальну область видимості.
Локальні змінні можуть мати те саме імʼя, що й глобальна змінна. У такому разі локальна змінна «затіняє» глобальну. Наприклад, локальна змінна, оголошена у функції, приховує глобальну змінну з тим самим імʼям: звернення та присвоєння стосуються локальної змінної, а глобальна залишається незмінною. Коли функція завершується, глобальна змінна знову стає видимою.
x=5
myfunc () {
local x=100
echo "in my function, $x == 100"
}
echo "in the global scope, $x == 5"
myfunc
echo "back in the global scope, $x == 5"
Це виводить:
in the global scope, 5 == 5
in my function, 100 == 100
back in the global scope, 5 == 5
Усередині функції можна звертатися до змінних з області видимості тієї функції, що її викликала. Це означає, що можна використовувати глобальні змінні, а також локальні змінні, оголошені у функції, що викликала поточну, або в якійсь функції вище за стеком викликів.
Строго кажучи, слово «глобальний» тут не зовсім доречне. Щоб розкрити змінну у функції, Bash підіймається стеком викликів аж до глобальної області видимості, шукаючи функцію, де було оголошено змінну з таким імʼям.
Цей приклад адаптовано з розділу Функції оболонки посібника:
func1() {
local var='func1 local'
func2
}
func2() {
echo "In func2, var = $var"
}
var=global
func1
func2
Результат:
In func2, var = func1 local
In func2, var = global
Так само присвоєння значення змінній відбувається в тій області видимості, де її було оголошено. Ця «дія на відстані» може створювати код, за яким важко стежити, бо не завжди очевидно, де саме змінній було присвоєно значення.
Стек викликів можна дослідити за допомогою змінної-масиву FUNCNAME.
Функція, як і будь-яка команда, має статус виходу. Типово статус функції - це статус виходу останньої виконаної команди.
За допомогою команди return можна вийти з функції з конкретним статусом виходу.
check_password () {
if [[ $1 == "secret" ]]; then
return 0
else
return 1
fi
}
read -sp "Enter your password: " pass
if check_password "$pass"; then
echo "Correct!"
else
echo "Wrong password."
fi
Використання return без аргументів повертає статус останньої виконаної команди.
Зауважмо, що функцію check_password можна спростити до:
check_password () { [[ $1 == "secret" ]]; }
[[...]] має статус виходу: 0 означає «правда», 1 означає «неправда».{...} перед закриваючою фігурною дужкою має бути або символ нового рядка, або крапка з комою.Статус виходу функції - це лише число. А як функція може щось вивести?
Функція може виводити в стандартний вивід. Щоб перехопити його, скористаймося знайомою підстановкою команд:
d6 () { echo "$(( 1 + RANDOM % 6 ))"; }
die=$( d6 )
echo "You rolled a $die."
Статус виходу функції доступний навіть тоді, коли ми перехоплюємо виведення.
roll () {
local n=$1
if (( 4 <= n && n <= 20 )); then
echo "$(( 1 + RANDOM % n ))" # exit status is 0
else
return 1
fi
}
read -p "How many faces does your die have? " faces
if die=$( roll "$faces" ); then
echo "You rolled a $die."
else
echo "I can't roll a die with $faces faces."
fi
Функції можуть викликати самі себе рекурсивно. Типово глибина рекурсії нічим не обмежена.
Приклад:
fibonacci() {
local n=$1
if (( n <= 1 )); then
echo "1"
else
local a=$(fibonacci "$(( n - 1 ))")
local b=$(fibonacci "$(( n - 2 ))")
echo "$(( a + b ))"
fi
}
for i in {1..10}; do fibonacci "$i"; done
# => 1
# => 2
# => 3
# => 5
# => 8
# => 13
# => 21
# => 34
# => 55
# => 89
Глибину рекурсії можна контролювати за допомогою змінної FUNCNEST.
bash -c '
recur() {
echo $1
recur $(($1 + 1))
}
FUNCNEST=5
recur 1
'
1
2
3
4
5
environment: line 1: recur: maximum function nesting level exceeded (5)