Kilka razy, kiedy czytałem o programowaniu, natknąłem się na koncepcję „oddzwaniania”.
Co zabawne, nigdy nie znalazłem wyjaśnienia, które mogę nazwać „dydaktycznym” lub „jasnym” dla tego terminu „funkcja zwrotna” (prawie każde wyjaśnienie, które przeczytałem, wydawało mi się dość odmienne od drugiego i czułem się zdezorientowany).
Czy w Bash istnieje koncepcja programowania zwrotnego? Jeśli tak, odpowiedz na mały, prosty przykład Bash.
declarative.bash
Interesujące może być środowisko , które wyraźnie wykorzystuje funkcje skonfigurowane do wywoływania, gdy potrzebna jest dana wartość.Odpowiedzi:
W typowym programowaniu imperatywnym piszesz sekwencje instrukcji i są one wykonywane jeden po drugim, z wyraźnym przepływem sterowania. Na przykład:
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):
Jeśli chcesz to wypróbować samodzielnie, zapisz powyższe w pliku, powiedzmy
cleanUpOnExit.sh
, wykonaj go i uruchom:Mój kod tutaj nigdy nie wywołuje jawnie
cleanup
funkcji; mówi Basha kiedy to nazwać, używająctrap cleanup EXIT
, czyli „droga Bash, należy uruchomićcleanup
komendę przy wyjściu” (acleanup
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 tutajcleanup
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ć
(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łaniabackup
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ównodoonall
ibackup
może być używany w innych kontekstach, co pozwala na bardziej kodu ponowne wykorzystanie, lepszych testów itpW tym przypadku wywołanie zwrotne jest
backup
funkcją, którą nakazujemydoonall
„oddzwonić” przy każdym z pozostałych argumentów - zapewniamydoonall
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).
źródło
Po pierwsze, należy zauważyć, że to, co czyni funkcję funkcją zwrotną, to sposób jej użycia, a nie to, co robi. Oddzwonienie ma miejsce, gdy kod, który piszesz, jest wywoływany z kodu, którego nie napisałeś. Poprosisz system, aby oddzwonił, gdy wydarzy się jakieś szczególne zdarzenie.
Przykładem wywołania zwrotnego w programowaniu powłoki są pułapki. Pułapka to wywołanie zwrotne, które nie jest wyrażone jako funkcja, ale jako fragment kodu do oceny. Pytasz powłokę o wywołanie kodu, gdy powłoka odbierze określony sygnał.
Innym przykładem wywołania zwrotnego jest
-exec
działaniefind
polecenia. Zadaniem tegofind
polecenia jest rekurencyjne przeglądanie katalogów i przetwarzanie każdego pliku po kolei. Domyślnie przetwarzanie ma na celu wydrukowanie nazwy pliku (niejawne-print
), ale wraz-exec
z przetwarzaniem jest uruchomienie określonego polecenia. Jest to zgodne z definicją wywołania zwrotnego, chociaż w przypadku wywołań zwrotnych nie jest zbyt elastyczne, ponieważ wywołanie zwrotne działa w osobnym procesie.Jeśli zaimplementowano funkcję wyszukiwania, można użyć funkcji wywołania zwrotnego do wywołania każdego pliku. Oto bardzo uproszczona funkcja znajdowania, która przyjmuje jako argument nazwę funkcji (lub zewnętrzną nazwę polecenia) i wywołuje ją na wszystkich zwykłych plikach w bieżącym katalogu i jego podkatalogach. Funkcja jest używana jako wywołanie zwrotne, które jest wywoływane za każdym razem, gdy
call_on_regular_files
znajdzie zwykły plik.Wywołania zwrotne nie są tak powszechne w programowaniu powłoki, jak w niektórych innych środowiskach, ponieważ powłoki są zaprojektowane głównie dla prostych programów. Oddzwanianie jest bardziej powszechne w środowiskach, w których przepływ danych i kontroli ma większe szanse na przechodzenie między częściami kodu, które są pisane i dystrybuowane niezależnie: system podstawowy, różne biblioteki, kod aplikacji.
źródło
foreach_server() { declare callback="$1"; declare server; for server in 192.168.0.1 192.168.0.2 192.168.0.3; do "$callback" "$server"; done; }
który można uruchomić jakoforeach_server echo
,foreach_server nslookup
itpdeclare callback="$1"
jest tak proste, jak może dostać choć: callback musi zostać przekazany w gdzieś, lub to nie jest oddzwanianie.„wywołania zwrotne” to tylko funkcje przekazywane jako argumenty do innych funkcji.
Na poziomie powłoki oznacza to po prostu skrypty / funkcje / polecenia przekazywane jako argumenty do innych skryptów / funkcji / poleceń.
Teraz, dla prostego przykładu, rozważ następujący skrypt:
mając streszczenie
zastosuje się
filter
do każdegofile
argumentu, a następnie wywołajcommand
z wyjściami filtrów jako argumentami.Na przykład:
To bardzo blisko tego, co możesz zrobić w seplenieniu (tylko żart ;-))
Niektóre osoby nalegają na ograniczenie terminu „wywołanie zwrotne” do „modułu obsługi zdarzeń” i / lub „zamknięcia” (krotka funkcja + dane / środowisko); w żadnym wypadku nie jest to ogólnie przyjęte znaczenie. Jednym z powodów, dla których „oddzwanianie” w tych wąskich zmysłach nie ma większego zastosowania w powłoce, jest to, że potoki + równoległość + możliwości programowania dynamicznego są o wiele potężniejsze i już za nie płacisz pod względem wydajności, nawet jeśli spróbuj użyć powłoki jako niezgrabnej wersji
perl
lubpython
.źródło
%
interpolacji w filtrach, cała sprawa może być zmniejszona do:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Ale to o wiele mniej użyteczne i ilustrujące imho.$1 <($2 "$3") <($2 "$4")
Rodzaj.
Jednym prostym sposobem na implementację wywołania zwrotnego w bash jest zaakceptowanie nazwy programu jako parametru, który działa jako „funkcja wywołania zwrotnego”.
Byłoby to wykorzystane w następujący sposób:
Oczywiście nie masz zamknięć w bash. Dlatego funkcja oddzwaniania nie ma dostępu do zmiennych po stronie wywołującej. Możesz jednak przechowywać dane potrzebne do wywołania zwrotnego w zmiennych środowiskowych. Przekazywanie informacji z powrotem do skryptu wywołującego jest trudniejsze. Dane mogą być umieszczone w pliku.
Jeśli twój projekt pozwala, aby wszystko było obsługiwane w jednym procesie, możesz użyć funkcji powłoki dla wywołania zwrotnego, aw tym przypadku funkcja wywołania zwrotnego ma oczywiście dostęp do zmiennych po stronie wywołującego.
źródło
Wystarczy dodać kilka słów do pozostałych odpowiedzi. Funkcja oddzwaniania działa na funkcje poza funkcją, która oddzwania. Aby było to możliwe, do funkcji wywołującej musi zostać przekazana cała definicja funkcji, która ma zostać wywołana, lub jej kod powinien być dostępny dla funkcji wywołującej.
To pierwsze (przekazanie kodu do innej funkcji) jest możliwe, chociaż pominię przykład, który wiązałby się ze złożonością. To ostatnie (przekazywanie funkcji po nazwie) jest powszechną praktyką, ponieważ zmienne i funkcje zadeklarowane poza zakresem jednej funkcji są dostępne w tej funkcji, o ile ich definicja poprzedza wywołanie funkcji, która na nich działa (co z kolei , co ma zostać zadeklarowane przed wywołaniem).
Zauważ też, że podobnie dzieje się podczas eksportowania funkcji. Powłoka, która importuje funkcję, może mieć gotowy szkielet i tylko czeka na definicje funkcji, aby je uruchomić. Eksport funkcji jest obecny w Bash i powodował wcześniej poważne problemy, btw (tak zwane Shellshock):
Uzupełnię tę odpowiedź jeszcze jedną metodą przekazania funkcji do innej funkcji, która nie jest wyraźnie obecna w Bash. Ten przekazuje go według adresu, a nie imienia. Można to znaleźć na przykład w Perlu. Bash nie oferuje w ten sposób ani funkcji, ani zmiennych. Ale jeśli, jak oświadczasz, chcesz mieć szerszy obraz z Bash jako przykładem, powinieneś wiedzieć, że kod funkcji może znajdować się gdzieś w pamięci, a kod ten może być dostępny przez to miejsce w pamięci, które jest zwany jego adresem.
źródło
Jednym z najprostszych przykładów wywołania zwrotnego w bash jest to, że wiele osób jest zaznajomionych, ale nie zdaje sobie sprawy, z jakiego wzorca projektowego faktycznie korzystają:
cron
Cron pozwala na określenie pliku wykonywalnego (binarnego lub skryptu), który program cron oddzwoni, gdy zostaną spełnione określone warunki (specyfikacja czasu)
Załóżmy, że masz skrypt o nazwie
doEveryDay.sh
. Nieskryptowym sposobem napisania skryptu jest:Sposób wywołania zwrotnego do napisania jest po prostu:
Następnie w crontab ustawiłbyś coś takiego
Nie trzeba wtedy pisać kodu, aby czekać na uruchomienie zdarzenia, ale zamiast tego polegać na
cron
ponownym wywołaniu kodu.Teraz zastanów się, JAK napisałeś ten kod w bash.
Jak wykonałbyś inny skrypt / funkcję w bash?
Napiszmy funkcję:
Teraz masz utworzoną funkcję, która akceptuje oddzwonienie. Możesz po prostu nazwać to tak:
Oczywiście funkcja co 24 godziny nigdy nie powraca. Bash jest nieco wyjątkowy, ponieważ możemy bardzo łatwo uczynić go asynchronicznym i spawnować proces, dodając
&
:Jeśli nie chcesz tego jako funkcji, możesz to zrobić jako skrypt:
Jak widać, wywołania zwrotne w bash są banalne. Jest to po prostu:
A oddzwonienie to po prostu:
Jak widać powyżej, wywołania zwrotne rzadko są bezpośrednią cechą języków. Zazwyczaj programują w sposób kreatywny, wykorzystując istniejące funkcje językowe. Każdy język, który może przechowywać wskaźnik / referencję / kopię jakiegoś bloku kodu / funkcji / skryptu, może implementować wywołania zwrotne.
źródło
watch
ifind
(gdy są używane z-exec
parametrem)Oddzwonienie to funkcja wywoływana, gdy wystąpi jakieś zdarzenie. Dzięki
bash
temu jedyny dostępny mechanizm obsługi zdarzeń jest powiązany z sygnałami, wyjściem powłoki i rozszerzony na zdarzenia błędów powłoki, zdarzenia debugowania i skrypty funkcji / źródła zwracane zdarzenia.Oto przykład bezużytecznych, ale prostych pułapek sygnału zwrotnego.
Najpierw utwórz skrypt implementujący wywołanie zwrotne:
Następnie uruchom skrypt w jednym terminalu:
$ ./callback-example
a na innym wyślij
USR1
sygnał do procesu powłoki.Każdy wysłany sygnał powinien wyzwalać wyświetlanie linii takich jak te w pierwszym terminalu:
ksh93
, jako powłoka implementująca wielebash
późniejszych funkcji , zapewnia tak zwane „funkcje dyscypliny”. Funkcje te, niedostępne zbash
, są wywoływane, gdy zmienna powłoki jest modyfikowana lub odwoływana (tj. Czytana). To otwiera drogę do ciekawszych aplikacji sterowanych zdarzeniami.Na przykład ta funkcja umożliwiła zaimplementowanie wywołań zwrotnych w stylu X11 / Xt / Motif w widżetach graficznych w starej wersji
ksh
dołączonych rozszerzeń graficznych o nazwiedtksh
. Zobacz podręcznik dksh .źródło