У типовому імперативному програмуванні ви пишете послідовності інструкцій, і вони виконуються одна за одною, з явним потоком управління. Наприклад:
if [ -f file1 ]; then # If file1 exists ...
cp file1 file2 # ... create file2 as a copy of a file1
fi
тощо.
Як видно з прикладу, при імперативному програмуванні ви досить легко стежите за потоком виконання, завжди працюючи з будь-якого заданого рядка коду, щоб визначити його контекст виконання, знаючи, що будь-які введені вами інструкції будуть виконані в результаті їх розташування в потоці (або місця їх дзвінків, якщо ви пишете функції).
Як зворотні виклики змінюють потік
Коли ви використовуєте зворотний зв'язок, замість того, щоб використовувати набір інструкцій «географічно», ви описуєте, коли його слід викликати. Типовими прикладами в інших середовищах програмування є такі випадки, як "завантажте цей ресурс, і коли завантаження завершиться, викликайте цей зворотний дзвінок". Bash не має узагальненої конструкції зворотного виклику подібного типу, але у нього є зворотні виклики для обробки помилок та кількох інших ситуацій; наприклад (для початку потрібно зрозуміти режими заміни команд та режими виходу Bash, щоб зрозуміти цей приклад):
#!/bin/bash
scripttmp=$(mktemp -d) # Create a temporary directory (these will usually be created under /tmp or /var/tmp/)
cleanup() { # Declare a cleanup function
rm -rf "${scripttmp}" # ... which deletes the temporary directory we just created
}
trap cleanup EXIT # Ask Bash to call cleanup on exit
Якщо ви хочете спробувати це самостійно, збережіть вищезазначене у файлі, скажімо cleanUpOnExit.sh
, зробіть його виконуваним і запустіть його:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Мій код тут ніколи прямо не викликає cleanup
функцію; він повідомляє Bash, коли його зателефонувати, використовуючи trap cleanup EXIT
, наприклад, "Шановний Баш, будь ласка, запустіть cleanup
команду, коли ви виходите" (і cleanup
це буде функцією, яку я визначив раніше, але це може бути все, що Баш розуміє). Bash підтримує це для всіх не фатальних сигналів, виходів, відмов команд та загальної налагодження (ви можете вказати зворотний виклик, який виконується перед кожною командою). Зворотний виклик тут - це cleanup
функція, яку Bash "передзвонив" безпосередньо перед виходом оболонки.
Ви можете використовувати здатність Bash оцінювати параметри оболонки як команди, будувати структуру, орієнтовану на зворотний виклик; це дещо виходить за рамки цієї відповіді, і, можливо, може викликати більше плутанини, припускаючи, що передача функцій завжди включає зворотний зв'язок. Див. Розділ Bash: передайте функцію як параметр для деяких прикладів основної функціональності. Ідея тут, як і у випадку зворотних викликів для обробки подій, полягає в тому, що функції можуть приймати дані як параметри, а також інші функції - це дозволяє абонентам надавати поведінку, а також дані. Простий приклад такого підходу може виглядати так
#!/bin/bash
doonall() {
command="$1"
shift
for arg; do
"${command}" "${arg}"
done
}
backup() {
mkdir -p ~/backup
cp "$1" ~/backup
}
doonall backup "$@"
(Я знаю, що це трохи марно, оскільки cp
може працювати з декількома файлами, це лише для ілюстрації.)
Тут ми створюємо функцію, doonall
яка приймає іншу команду, задану як параметр, і застосовує її до решти її параметрів; тоді ми використовуємо це для виклику backup
функції за всіма параметрами, заданими сценарієм. В результаті виходить скрипт, який копіює всі свої аргументи по черзі в каталог резервного копіювання.
Такий підхід дозволяє записувати функції з єдиними обов'язками: doonall
відповідальність полягає в тому, щоб виконувати щось за всіма своїми аргументами, по одному; backup
Відповідальність полягає в тому, щоб зробити копію свого (єдиного) аргументу в резервному каталозі. І те, doonall
і backup
інше може бути використане в інших контекстах, що дозволяє більше використовувати код, кращі тести тощо.
У цьому випадку зворотний виклик - це backup
функція, яку ми кажемо doonall
«передзвонити» на кожному з інших аргументів - ми надаємо doonall
поведінку (її перший аргумент), а також дані (решта аргументів).
(Зауважте, що у випадку використання, продемонстрованому у другому прикладі, я не використовував би сам термін "зворотний виклик", але це, можливо, звичка, що випливає з мов, якими я користуюся. Я вважаю це як передачу функцій чи лямбда навколо , а не реєструвати зворотні дзвінки в системі, орієнтованій на події.)