W typowym programowaniu imperatywnym piszesz sekwencje instrukcji i są one wykonywane jeden po drugim, z wyraźnym przepływem sterowania. Na przykład:
if [ -f file1 ]; then # If file1 exists ...
cp file1 file2 # ... create file2 as a copy of a file1
fi
itp.
Jak widać z przykładu, w programowaniu imperatywnym dość łatwo podążasz za procesem wykonywania, zawsze pracując w górę od dowolnego wiersza kodu, aby określić jego kontekst wykonania, wiedząc, że wszelkie instrukcje, które podasz, zostaną wykonane w wyniku ich lokalizacja w przepływie (lub lokalizacje ich witryn wywołujących, jeśli piszesz funkcje).
Jak wywołania zwrotne zmieniają przepływ
Kiedy używasz wywołań zwrotnych, zamiast umieszczania zestawu instrukcji „geograficznie”, określasz, kiedy należy go wywołać. Typowymi przykładami w innych środowiskach programowych są przypadki takie jak „pobierz ten zasób, a gdy pobieranie zostanie ukończone, oddzwoń”. Bash nie ma takiej ogólnej konstrukcji wywołania zwrotnego, ale ma wywołania zwrotne do obsługi błędów i kilku innych sytuacji; na przykład (najpierw trzeba zrozumieć podstawianie poleceń i tryby wyjścia Bash, aby zrozumieć ten przykład):
#!/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
Jeśli chcesz to wypróbować samodzielnie, zapisz powyższe w pliku, powiedzmy cleanUpOnExit.sh
, wykonaj go i uruchom:
chmod 755 cleanUpOnExit.sh
./cleanUpOnExit.sh
Mój kod tutaj nigdy nie wywołuje jawnie cleanup
funkcji; mówi Basha kiedy to nazwać, używając trap cleanup EXIT
, czyli „droga Bash, należy uruchomić cleanup
komendę przy wyjściu” (a cleanup
zdarza się to funkcja I zdefiniowane wcześniej, ale może to być cokolwiek Bash rozumie). Bash obsługuje to dla wszystkich niekrytycznych sygnałów, wyjść, błędów poleceń i ogólnego debugowania (możesz określić wywołanie zwrotne uruchamiane przed każdą komendą). Oddzwanianie jest tutaj cleanup
funkcją, która jest „wywoływana” przez Basha tuż przed wyjściem powłoki.
Możesz użyć zdolności Basha do oceny parametrów powłoki jako poleceń, aby zbudować strukturę zorientowaną na wywołanie zwrotne; jest to nieco poza zakresem tej odpowiedzi i być może spowodowałoby więcej zamieszania, sugerując, że przekazywanie funkcji zawsze wiąże się z wywołaniami zwrotnymi. Zobacz Bash: przekaż funkcję jako parametr dla niektórych przykładów podstawowej funkcjonalności. Pomysł tutaj, podobnie jak w przypadku wywołań zwrotnych obsługi zdarzeń, polega na tym, że funkcje mogą przyjmować dane jako parametry, ale także inne funkcje - pozwala to dzwoniącym na zachowanie i dane. Prosty przykład takiego podejścia może wyglądać
#!/bin/bash
doonall() {
command="$1"
shift
for arg; do
"${command}" "${arg}"
done
}
backup() {
mkdir -p ~/backup
cp "$1" ~/backup
}
doonall backup "$@"
(Wiem, że jest to trochę bezużyteczne, ponieważ cp
może poradzić sobie z wieloma plikami, to tylko dla ilustracji.)
Tutaj tworzymy funkcję, doonall
która pobiera inne polecenie podane jako parametr i stosuje je do pozostałych parametrów; następnie używamy tego do wywołania backup
funkcji dla wszystkich parametrów podanych w skrypcie. Wynikiem jest skrypt, który kopiuje wszystkie argumenty, jeden po drugim, do katalogu kopii zapasowej.
Tego rodzaju podejście pozwala na pisanie funkcji z pojedynczymi obowiązkami: doonall
odpowiedzialność polega na tym, aby uruchomić wszystkie argumenty, pojedynczo; backup
obowiązkiem jest wykonanie kopii (jedynego) argumentu w katalogu kopii zapasowej. Zarówno doonall
i backup
może być używany w innych kontekstach, co pozwala na bardziej kodu ponowne wykorzystanie, lepszych testów itp
W tym przypadku wywołanie zwrotne jest backup
funkcją, którą nakazujemy doonall
„oddzwonić” przy każdym z pozostałych argumentów - zapewniamy doonall
zachowanie (pierwszy argument), a także dane (pozostałe argumenty).
(Zauważ, że w przypadku użycia pokazanym w drugim przykładzie sam nie użyłbym terminu „oddzwanianie”, ale być może jest to nawyk wynikający z języków, których używam. Myślę o tym jako o przekazywaniu funkcji lub lambdas w pobliżu , zamiast rejestrować połączenia zwrotne w systemie zorientowanym na zdarzenie).