- концепция «обратного вызова» программирования существующего в Bash?

Я думаю, вы могли бы сделать что-то подобное :

find . -type f -mtime +10 -print0 | sort -n -r -k1 | while IFS= read -r -d $'\0' line; do
    #echo "$line"
    ls -lS "$line"    
done

обратите внимание на -S параметр команды ls :

-S sort by file size

21
26.09.2018, 21:34
6 ответов

В типичном императивном программировании вы пишете последовательности инструкций, которые выполняются одна за другой с явным потоком управления. Например:

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, , то есть «дорогой Bash, пожалуйста, запустите команду cleanupпри выходе» (и cleanupоказывается функцией, которую я определил ранее, но это может быть все, что понимает Bash ). 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поведение (ее первого аргумента )как а также данные (остальные аргументы ).

(Обратите внимание, что в случае использования -, продемонстрированном во втором примере, я бы сам не использовал термин «обратный вызов», но, возможно, это привычка, вытекающая из языков, которые я использую. Я думаю об этом как о передаче функций или лямбда-выражений, а не о регистрации обратных вызовов в системе, ориентированной на события -.)

49
27.01.2020, 19:43

Во-первых, важно отметить, что функцию обратного вызова делает то, как она используется, а не то, что она делает. Обратный вызов — это когда код, который вы пишете, вызывается из кода, который вы не писали. Вы просите систему перезвонить вам, когда произойдет определенное событие.

Примером обратного вызова в программировании оболочки являются ловушки. Ловушка — это обратный вызов, который выражается не как функция, а как фрагмент кода для оценки. Вы просите оболочку вызывать ваш код, когда оболочка получает определенный сигнал.

Другим примером обратного вызова является действие -execкоманды find. Работа команды findзаключается в рекурсивном обходе каталогов и обработке каждого файла по очереди. По умолчанию обработка должна напечатать имя файла (неявное -print), но с -execобработка должна выполнить команду, которую вы укажете. Это соответствует определению обратного вызова, хотя callbacks go не очень гибок, поскольку обратный вызов выполняется в отдельном процессе.

Если бы вы реализовали функцию, подобную find -, вы могли бы заставить ее использовать функцию обратного вызова для вызова каждого файла. Вот ультра -упрощенная функция поиска -, которая принимает имя функции (или имя внешней команды )в качестве аргумента и вызывает ее для всех обычных файлов в текущем каталоге и его подкаталогах. Функция используется как обратный вызов, который вызывается каждый раз, когда call_on_regular_filesнаходит обычный файл.

shopt -s globstar
call_on_regular_files () {
  declare callback="$1"
  declare file
  for file in **/*; do
    if [[ -f $file ]]; then
      "$callback" "$file"
    fi
  done
}

Обратные вызовы не так распространены в программировании оболочки, как в некоторых других средах, потому что оболочки в основном предназначены для простых программ. Обратные вызовы более распространены в средах, где поток данных и управления с большей вероятностью перемещается туда и обратно между частями кода, написанными и распространяемыми независимо :от базовой системы, различных библиотек и кода приложения.

27
27.01.2020, 19:43

Просто добавлю несколько слов к другим ответам. Обратный вызов функции работает с функциями (и ), внешними по отношению к функции, которая вызывает обратный вызов. Чтобы это стало возможным, либо полное определение вызываемой функции должно быть передано вызывающей функции, либо ее код должен быть доступен для вызывающей функции.

Предыдущая (передача кода в другую функцию )возможна, хотя я пропущу пример, так как это потребует сложности. Последняя (передача функции по имени )является обычной практикой, поскольку переменные и функции, объявленные вне области действия одной функции, доступны в этой функции, если их определение предшествует вызову функции, которая с ними работает (, который, в свою очередь, должен быть объявлен перед вызовом ).

Также обратите внимание, что подобное происходит при экспорте функций. Оболочка, которая импортирует функцию, может иметь готовую структуру и просто ждать определения функций, чтобы привести их в действие. Экспорт функции присутствует в Bash и ранее вызывал серьезные проблемы, кстати (, которые назывались Shellshock ):

.

Я дополню этот ответ еще одним методом передачи функции в другую функцию, который явно не присутствует в Bash. Этот передает его по адресу, а не по имени. Это можно найти, например, в Perl. Bash не предлагает такого способа ни для функций, ни для переменных. Но если, как вы заявляете, вы хотите иметь более широкую картину с Bash в качестве примера, то вам следует знать, что код функции может находиться где-то в памяти, и к этому коду можно получить доступ из этой области памяти, которая назвал его адрес.

3
27.01.2020, 19:43

Одним из простейших примеров обратного вызова в bash является тот, с которым многие знакомы, но не понимают, какой шаблон проектирования они на самом деле используют:

хрон

Cron позволяет указать исполняемый файл (двоичный файл или сценарий ), который программа cron будет вызывать при выполнении некоторых условий (указание времени)

Допустим, у вас есть скрипт с именем doEveryDay.sh. Способ обратного вызова без -для написания скрипта —:

#! /bin/bash
while true; do
    doSomething
    sleep $TWENTY_FOUR_HOURS
done

Обратный вызов позволяет написать это просто:

#! /bin/bash
doSomething

Затем в crontab нужно указать что-то вроде

0 0 * * *     doEveryDay.sh

Тогда вам не нужно будет писать код для ожидания запуска события, а вместо этого полагаться на cronдля обратного вызова вашего кода.


Теперь подумайте, КАК бы вы написали этот код в bash.

Как бы вы выполнили другой скрипт/функцию в bash?

Напишем функцию:

function every24hours () {
    CALLBACK=$1 ;# assume the only argument passed is
                 # something we can "call"/execute
    while true; do
        $CALLBACK ;# simply call the callback
        sleep $TWENTY_FOUR_HOURS
    done
}

Теперь вы создали функцию, которая принимает обратный вызов. Вы можете просто назвать это так:

# "ping" google website every day
every24hours 'curl google.com'

Конечно, функция every24hours никогда не возвращается. Bash немного уникален тем, что мы можем очень легко сделать его асинхронным и запустить процесс, добавив&:

every24hours 'curl google.com' &

Если вы не хотите использовать это как функцию, вы можете сделать это как скрипт:

#every24hours.sh
CALLBACK=$1 ;# assume the only argument passed is
               # something we can "call"/execute
while true; do
    $CALLBACK ;# simply call the callback
    sleep $TWENTY_FOUR_HOURS
done

Как видите, обратные вызовы в bash тривиальны. Это просто:

CALLBACK_SCRIPT=$3 ;# or some other 
                    # argument to 
                    # function/script

И вызов обратного вызова просто:

$SOME_CALLBACK_FUNCTION_OR_SCRIPT

Как вы можете видеть из формы выше, обратные вызовы редко являются непосредственными функциями языков. Обычно они программируют творчески, используя существующие возможности языка. Любой язык, который может хранить указатель/ссылку/копию некоторого блока кода/функции/скрипта, может реализовать обратные вызовы.

2
27.01.2020, 19:43

Обратный вызов — это функция, вызываемая при возникновении некоторого события. В bashединственный имеющийся механизм обработки событий связан с сигналами, выходом из оболочки и расширен до событий ошибок оболочки, событий отладки и событий возврата функций/исходных сценариев.

Вот пример бесполезного, но простого обратного вызова, использующего сигнальные ловушки.

Сначала создайте скрипт, реализующий обратный вызов:

#!/bin/bash

myCallback() {
    echo "I've been called at $(date +%Y%m%dT%H%M%S)"
}

# Set the handler
trap myCallback SIGUSR1

# Main loop. Does nothing useful, essentially waits
while true; do
    read foo
done

Затем запустите скрипт в одном терминале:

$./обратный вызов -пример

, а на другом отправьте сигнал USR1процессу оболочки.

$ pkill -USR1 callback-example

Каждый отправленный сигнал должен вызывать отображение строк, подобных этим, в первом терминале.:

I've been called at 20180925T003515
I've been called at 20180925T003517

ksh93, как оболочка, реализующая многие функции, которые позже были приняты bash, обеспечивает то, что она называет «дисциплинарными функциями». Эти функции, недоступные в bash, вызываются при изменении или ссылке на переменную оболочки (, т. е. при чтении ). Это открывает путь к более интересным приложениям, управляемым событиями.

Например, эта функция позволяла реализовать обратные вызовы в стиле X11/Xt/Motif для графических виджетов в старой версии ksh, которая включала графические расширения под названием dtksh. См. руководство по dksh .

1
27.01.2020, 19:43

Типа.

Один простой способ реализации обратного вызова в bash — принять имя программы в качестве параметра, который действует как «функция обратного вызова».

# This is script worker.sh accepts a callback in $1
cb="$1"
....
# Execute the call back, passing 3 parameters
$cb foo bar baz

Это можно использовать так:

# Invokes mycb.sh as a callback
worker.sh mycb.sh

Конечно, в bash нет замыканий. Следовательно, функция обратного вызова не имеет доступа к переменным на стороне вызывающей стороны. Однако вы можете хранить данные, необходимые для обратного вызова, в переменных среды. Обратную передачу информации из обратного вызова сценарию вызывающего более сложно. Данные могут быть помещены в файл.

Если ваш дизайн позволяет обрабатывать все в одном процессе, вы можете использовать функцию оболочки для обратного вызова, и в этом случае функция обратного вызова, конечно, имеет доступ к переменным на стороне инициатора.

4
27.01.2020, 19:43

Теги

Похожие вопросы