Несколько раз, читая о программировании, я сталкивался с концепцией «обратного вызова».
Как ни странно, я так и не нашел объяснения, которое я мог бы назвать «дидактическим» или «понятным» для этого термина «функция обратного вызова» (почти любое прочитанное мной объяснение показалось мне достаточно отличным от другого, и я почувствовал растерянность).
Существует ли в Bash концепция обратного вызова программирования? Если это так, пожалуйста, ответьте с небольшим, простым примером Bash.
declarative.bash
интересным, как платформу, которая явно использует функции, сконфигурированные для вызова, когда необходимо данное значение.Ответы:
В типичном императивном программировании вы пишете последовательности инструкций, и они выполняются одна за другой с явным потоком управления. Например:
и т.п.
Как видно из примера, в императивном программировании вы достаточно легко следите за потоком выполнения, всегда пробираясь вверх от любой строки кода, чтобы определить его контекст выполнения, зная, что любые инструкции, которые вы дадите, будут выполнены в результате их выполнения. местоположение в потоке (или местоположение их сайтов вызовов, если вы пишете функции).
Как обратные вызовы меняют поток
Когда вы используете обратные вызовы, вместо того, чтобы использовать набор инструкций «географически», вы описываете, когда он должен быть вызван. Типичными примерами в других средах программирования являются такие случаи, как «загрузить этот ресурс, а когда загрузка будет завершена, вызвать этот обратный вызов». Bash не имеет общей конструкции обратного вызова такого рода, но он имеет обратные вызовы для обработки ошибок и некоторых других ситуаций; например (чтобы понять пример, нужно сначала понять подстановку команд и режимы выхода Bash ):
Если вы хотите попробовать это сами, сохраните вышеупомянутое в файле, скажем
cleanUpOnExit.sh
, сделайте его исполняемым и запустите его:Мой код здесь никогда явно не вызывает
cleanup
функцию; он сообщает Bash, когда его вызывать, используяtrap cleanup EXIT
, например, «дорогой Bash, пожалуйста, запуститеcleanup
команду при выходе» (иcleanup
это функция, которую я определил ранее, но это может быть все, что понимает Bash). Bash поддерживает это для всех нефатальных сигналов, выходов, сбоев команд и общей отладки (вы можете указать обратный вызов, который запускается перед каждой командой). Обратный вызов здесь - этоcleanup
функция, которую Bash «вызывает» перед выходом из оболочки.Вы можете использовать способность Bash оценивать параметры оболочки в виде команд, чтобы создать ориентированную на обратный вызов структуру; это несколько выходит за рамки этого ответа, и, возможно, приведет к еще большей путанице, если предположить, что передача функций всегда связана с обратными вызовами. См. Bash: передать функцию в качестве параметра для некоторых примеров базовой функциональности. Идея здесь, как и в случае обратных вызовов обработки событий, заключается в том, что функции могут принимать данные в качестве параметров, но также и другие функции - это позволяет вызывающим сторонам предоставлять поведение, а также данные. Простой пример такого подхода может выглядеть так
(Я знаю, что это немного бесполезно, поскольку
cp
может работать с несколькими файлами, это только для иллюстрации.)Здесь мы создаем функцию,
doonall
которая принимает другую команду, заданную в качестве параметра, и применяет ее к остальным ее параметрам; затем мы используем это для вызоваbackup
функции по всем параметрам, переданным скрипту. В результате получается скрипт, который копирует все свои аргументы, один за другим, в каталог резервных копий.Такой подход позволяет писать функции с единственной ответственностью:
doonall
ответственность состоит в том, чтобы запускать что-то по всем своим аргументам, по одному за раз;backup
ответственность заключается в том, чтобы сделать копию своего (единственного) аргумента в резервном каталоге. И то,doonall
и другоеbackup
можно использовать в других контекстах, что позволяет больше повторного использования кода, лучшие тесты и т. Д.В этом случае обратный вызов - это
backup
функция, которую мы говоримdoonall
«перезванивать» по каждому из ее других аргументов - мы предоставляемdoonall
поведение (его первый аргумент), а также данные (остальные аргументы).(Обратите внимание, что в случае использования, продемонстрированном во втором примере, я бы сам не использовал термин «обратный вызов», но, возможно, это привычка, вытекающая из языков, которые я использую. Я думаю об этом как о передаче функций или лямбд вокруг вместо регистрации обратных вызовов в ориентированной на события системе.)
источник
Во-первых, важно отметить, что то, что делает функцию функцией обратного вызова, - это то, как она используется, а не то, что она делает. Обратный вызов - это когда код, который вы пишете, вызывается из кода, который вы не написали. Вы просите систему перезвонить вам, когда происходит какое-то конкретное событие.
Примером обратного вызова в программировании оболочки являются ловушки. Ловушка - это обратный вызов, который выражается не как функция, а как кусок кода для оценки. Вы просите, чтобы оболочка вызывала ваш код, когда оболочка получает определенный сигнал.
Другим примером обратного вызова является
-exec
действиеfind
команды. Задачаfind
команды - рекурсивно обходить каталоги и обрабатывать каждый файл по очереди. По умолчанию обработка состоит в том, чтобы напечатать имя файла (неявное-print
), а при-exec
обработке - запустить указанную вами команду. Это соответствует определению обратного вызова, хотя при обратном вызове он не очень гибкий, так как обратный вызов выполняется в отдельном процессе.Если вы реализовали функцию поиска типа, вы можете использовать функцию обратного вызова для вызова каждого файла. Вот очень упрощенная функция поиска типа, которая принимает имя функции (или имя внешней команды) в качестве аргумента и вызывает его для всех обычных файлов в текущем каталоге и его подкаталогах. Функция используется в качестве обратного вызова, который вызывается каждый раз, когда
call_on_regular_files
находит обычный файл.Обратные вызовы не так распространены в программировании оболочки, как в некоторых других средах, потому что оболочки в первую очередь предназначены для простых программ. Обратные вызовы более распространены в средах, где поток данных и управления с большей вероятностью перемещается назад и вперед между частями кода, которые написаны и распределены независимо: базовая система, различные библиотеки, код приложения.
источник
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; }
который вы можете запустить какforeach_server echo
,foreach_server nslookup
и т. Д. Этоdeclare callback="$1"
примерно настолько просто, насколько это возможно получить: обратный вызов должен быть где-то передан, или это не обратный вызов.«Обратные вызовы» - это просто функции, передаваемые в качестве аргументов другим функциям.
На уровне оболочки это просто означает, что скрипты / функции / команды передаются в качестве аргументов другим скриптам / функциям / командам.
Теперь для простого примера рассмотрим следующий скрипт:
с кратким изложением
будет применяться
filter
к каждомуfile
аргументу, а затем вызыватьcommand
выходные данные фильтров в качестве аргументов.Например:
Это очень близко к тому, что вы можете сделать в lisp (шучу ;-))
Некоторые люди настаивают на ограничении термина «обратный вызов» термином «обработчик событий» и / или «закрытие» (кортеж функция + данные / окружение); это, никоим образом обычно принимается смысл. И одна из причин, по которой «обратные вызовы» в этих узких смыслах не очень полезны в оболочке, заключается в том, что каналы + параллелизм + возможности динамического программирования намного мощнее, и вы уже платите за них с точки зрения производительности, даже если вы попробуйте использовать оболочку как неуклюжую версию
perl
илиpython
.источник
%
интерполяции в фильтрах, все это может быть сведено к:cmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")
. Но это гораздо менее полезный и иллюстративный имхо.$1 <($2 "$3") <($2 "$4")
Вид.
Один простой способ реализовать обратный вызов в bash - это принять имя программы в качестве параметра, который действует как «функция обратного вызова».
Это будет использоваться так:
Конечно, у вас нет замыканий в bash. Следовательно, функция обратного вызова не имеет доступа к переменным на стороне вызывающей стороны. Однако вы можете хранить данные, необходимые для обратного вызова, в переменных среды. Передача информации обратно из обратного вызова в скрипт invoker сложнее. Данные могут быть помещены в файл.
Если ваш дизайн допускает, что все обрабатывается в одном процессе, вы можете использовать функцию оболочки для обратного вызова, и в этом случае функция обратного вызова, конечно, имеет доступ к переменным на стороне вызывающего.
источник
Просто чтобы добавить несколько слов к другим ответам. Функция обратного вызова работает с функциями, внешними по отношению к функции, которая вызывает вызов. Для того чтобы это было возможно, либо полное определение функции, которая должна быть вызвана обратно, необходимо передать функции, вызывающей функцию, либо ее код должен быть доступен функции, вызывающей функцию.
Первый вариант (передача кода в другую функцию) возможен, хотя я пропущу пример, поскольку это повлечет за собой сложность. Последнее (передача функции по имени) является обычной практикой, поскольку переменные и функции, объявленные вне области действия одной функции, доступны в этой функции, если их определение предшествует вызову функции, которая над ними работает (что, в свою очередь, , как будет объявлено до того, как он будет вызван).
Также обратите внимание, что подобное происходит и при экспорте функций. Оболочка, которая импортирует функцию, может иметь готовую платформу и просто ждать, пока определения функции приведут их в действие. Функция экспорта присутствует в Bash и вызывает ранее серьезные проблемы, кстати (это называлось Shellshock):
Я дополню этот ответ еще одним способом передачи функции в другую функцию, которая явно отсутствует в Bash. Этот передает его по адресу, а не по имени. Это можно найти в Perl, например. Bash предлагает этот способ ни для функций, ни для переменных. Но если, как вы заявляете, вы хотите иметь более широкую картинку с Bash в качестве примера, то вы должны знать, что код функции может находиться где-то в памяти, и этот код может быть доступен из той области памяти, которая назвал его адрес.
источник
Один из простейших примеров обратного вызова в bash - это тот, с которым многие знакомы, но не понимают, какой шаблон дизайна они на самом деле используют:
хрон
Cron позволяет вам указать исполняемый файл (двоичный файл или скрипт), который программа cron будет вызывать при выполнении некоторых условий (указание времени)
Скажем, у вас есть сценарий под названием
doEveryDay.sh
. Способ написания сценария без обратного вызова:Способ обратного вызова написать это просто:
Затем в crontab вы установите что-то вроде
Тогда вам не нужно было бы писать код для ожидания события, а вместо этого полагаться на
cron
обратный вызов кода.Теперь рассмотрим, как бы вы написали этот код на bash.
Как бы вы выполнили другой скрипт / функцию в bash?
Давайте напишем функцию:
Теперь вы создали функцию, которая принимает обратный вызов. Вы можете просто назвать это так:
Конечно, функция каждые 24 часа никогда не возвращается. Bash немного уникален тем, что мы можем очень легко сделать его асинхронным и запустить процесс, добавив
&
:Если вы не хотите использовать это как функцию, вместо этого вы можете сделать это как скрипт:
Как видите, обратные вызовы в bash тривиальны. Это просто:
И вызвать обратный вызов просто:
Как видно из приведенной выше формы, обратные вызовы редко являются прямым признаком языков. Они обычно программируют творчески, используя существующие языковые возможности. Любой язык, который может хранить указатель / ссылку / копию некоторого блока кода / функции / скрипта, может реализовать обратные вызовы.
источник
watch
иfind
(при использовании с-exec
параметром)Обратный вызов - это функция, вызываемая при возникновении какого-либо события. При
bash
этом единственный действующий механизм обработки событий связан с сигналами, выходом из оболочки и распространяется на события ошибок оболочки, события отладки и сценарии возврата функций / источников.Вот пример бесполезного, но простого обратного вызова, использующего сигнальные ловушки.
Сначала создайте скрипт, реализующий обратный вызов:
Затем запустите скрипт в одном терминале:
$ ./callback-example
и на другом, отправьте
USR1
сигнал процессу оболочки.Каждый отправленный сигнал должен вызывать отображение строк, подобных этим, в первом терминале:
ksh93
В качестве оболочки, реализующей многие функции, которыеbash
впоследствии были приняты, она предоставляет то, что она называет «дисциплинарными функциями». Эти функции, недоступные сbash
, вызываются, когда переменная оболочки модифицируется или на нее ссылаются (т. Е. Читает). Это открывает путь к более интересным приложениям, управляемым событиями.Например, эта функция позволяла реализовывать обратные вызовы в стиле X11 / Xt / Motif для графических виджетов в старой версии
ksh
, включающей в себя графические расширения, называемыеdtksh
. Смотрите руководство по dksh .источник