Фу

Функції у Bash

{one: "1 вправа", few: "%{count} вправи", many: "%{count} вправ", other: "%{count} вправи"}

Про концепцію Функції

Багато скриптів Bash написано в суто імперативному стилі: виконати одну команду, потім виконати іншу, і так далі. Іноді потрібно обʼєднати послідовність команд, які за задумом виконують одну спільну мету. Саме тут у пригоді стають функції.

Визначення функції

Функцію визначають так:

my_function () {
    COMMANDS
}

Порожні дужки просто означають, що ми визначаємо функцію. Усередину них нічого не пишемо.

Параметри функції

Визначені функції поводяться як будь-яка інша команда (вбудована чи ні). Як і будь-якій команді, своїм функціям можна передавати аргументи. Усередині функції ми звертаємося до аргументів через позиційні параметри, $1, $2 тощо. (Пригадаймо, що позиційні параметри ми вивчали в концепції Змінні.)

Advanced

Спеціальний параметр $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

Усередині функції можна звертатися до змінних з області видимості тієї функції, що її викликала. Це означає, що можна використовувати глобальні змінні, а також локальні змінні, оголошені у функції, що викликала поточну, або в якійсь функції вище за стеком викликів.

Advanced

Строго кажучи, слово «глобальний» тут не зовсім доречне. Щоб розкрити змінну у функції, 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

Так само присвоєння значення змінній відбувається в тій області видимості, де її було оголошено. Ця «дія на відстані» може створювати код, за яким важко стежити, бо не завжди очевидно, де саме змінній було присвоєно значення.

Advanced

Стек викликів можна дослідити за допомогою змінної-масиву 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 без аргументів повертає статус останньої виконаної команди.

Note

Зауважмо, що функцію check_password можна спростити до:

check_password () { [[ $1 == "secret" ]]; }
  1. Умовна конструкція [[...]] має статус виходу: 0 означає «правда», 1 означає «неправда».
  2. У конструкції групування {...} перед закриваючою фігурною дужкою має бути або символ нового рядка, або крапка з комою.

Виведення функції

Статус виходу функції - це лише число. А як функція може щось вивести?

Функція може виводити в стандартний вивід. Щоб перехопити його, скористаймося знайомою підстановкою команд:

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
Advanced

Глибину рекурсії можна контролювати за допомогою змінної 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)
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці