プログラミングの「コールバック」概念はBashに存在しますか?


21

プログラミングについて何度か読んだときに、「コールバック」の概念に出会いました。

おもしろいことに、この用語「コールバック関数」の「教訓的」または「明確」と呼べる説明は見つかりませんでした(読んだほとんどの説明は、私とはかなり違うようで、混乱していました)。

プログラミングの「コールバック」概念はBashに存在しますか?もしそうなら、小さく、シンプルな、バッシュの例で答えてください。


2
「コールバック」は実際の概念ですか、それとも「ファーストクラスの機能」ですか?
セドリックH.

declarative.bash特定の値が必要なときに呼び出されるように構成された関数を明示的に活用するフレームワークとして、興味深いことがあります。
チャールズダフィー

別の関連フレームワーク:bashup / events。そのドキュメントはなどなど検証のためなどのコールバックの使用の簡単なデモ、検索、多くの含み
PJイービ

1
@CedricH。あなたに投票しました。「「コールバック」は実際の概念ですか、それとも「ファーストクラスの機能」ですか?」
韻律-甲Vereableコンテキスト

コールバックは、「特定のイベントがトリガーされた後にコールバックされる関数」を意味すると理解しています。あれは正しいですか?
-JohnDoea

回答:


44

典型的な命令型プログラミングでは命令のシーケンスを記述し、明示的な制御フローで次々に実行されます。例えば:

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て、スクリプトに指定されたすべてのパラメーターで関数を呼び出します。その結果、すべての引数を1つずつバックアップディレクトリにコピーするスクリプトが作成されます。

この種のアプローチにより、関数を単一の責任で記述することができます。doonallの責任は、すべての引数に対して何かを1つずつ実行することです。backupの責任は、バックアップディレクトリにその(唯一の)引数のコピーを作成することです。両方doonall及びbackup他のコンテキストで使用することができ、より多くのコード再利用、より良いテスト等を可能にします

この場合、コールバックはbackup関数でありdoonall、他の各引数に対して「コールバック」するように指示しdoonallます。データ(残りの引数)と同様に動作(最初の引数)を提供します。

(2番目の例で示したようなユースケースでは、「コールバック」という用語は使用しませんが、それはおそらく使用する言語に起因する習慣です。これは、関数またはラムダを渡すことと考えられます。 、コールバックをイベント指向システムに登録するのではなく。)


25

最初に、関数をコールバック関数にするのは、その機能ではなく、その使用方法であることに注意することが重要です。コールバックは、あなたが書いたコードがあなたが書いていないコードから呼び出されるときです。特定のイベントが発生したときにコールバックするようにシステムに要求しています。

シェルプログラミングのコールバックの例は、トラップです。トラップは、関数としてではなく、評価するコードの一部として表現されるコールバックです。シェルが特定のシグナルを受信したときにコードを呼び出すようシェルに要求しています。

コールバックの別の例は-execfindコマンドのアクションです。findコマンドの仕事は、ディレクトリを再帰的に走査し、各ファイルを順番に処理することです。デフォルトでは、処理はファイル名(暗黙-print)を印刷しますが-exec、処理では指定したコマンドを実行します。これはコールバックの定義に適合しますが、コールバックでは別のプロセスで実行されるため、コールバックではあまり柔軟性がありません。

find-like関数を実装した場合は、コールバック関数を使用して各ファイルを呼び出すようにすることができます。これは、引数として関数名(または外部コマンド名)を取り、現在のディレクトリとそのサブディレクトリ内のすべての通常のファイルで呼び出す、非常に単純化された検索のような関数です。この関数は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
}

シェルは主に単純なプログラム用に設計されているため、コールバックはシェルプログラミングでは他のいくつかの環境ほど一般的ではありません。コールバックは、データおよび制御フローが、独立して記述および配布されるコードの部分(ベースシステム、さまざまなライブラリ、アプリケーションコード)の間を行き来する可能性が高い環境でより一般的です。


1
特にうまく説明
ロアイマ

1
@JohnDoeaアイデアは、それがあなたが本当に書く関数ではないという点で、それは超単純化されているということだと思います。おそらく単純な例では、上でコールバックを実行するようにハードコーディングされたリストを持つものになるだろう。しかし: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 echoforeach_server nslookupなど、declare callback="$1"コールバックはどこかに渡すことがありますけれども、それが得ることができるような単純なようについてですまたは、コールバックではありません。
IMSoP

4
「コールバックとは、あなたが書いたコードが、あなたが書いていないコードから呼び出されることです。」単に間違っています。いくつかの非ブロッキング非同期作業を行うものを記述し、完了時に実行されるコールバックで実行できます。コードの作成者に関係するものはありません。
mikemaccana

5
@mikemaccanaもちろん、同じ人がコードの2つの部分を書いた可能性もあります。しかし、それは一般的なケースではありません。私は概念の基本を説明しているが、正式な定義は与えていない。すべてのコーナーケースを説明すると、基本を伝えることは困難です。
ジル 'SO-悪であるのをやめる'

1
それを聞いてうれしい。コールバックを使用するコードとコールバックの両方を記述する人々が一般的ではないか、エッジケースであり、混乱のために、この答えが基本を伝えていることに同意しません。
ミケマッカナ

7

「コールバック」は、引数として他の関数に渡される関数です。

シェルレベルでは、他のスクリプト/関数/コマンドに引数として渡されるスクリプト/関数/コマンドを単に意味します。

ここで、簡単な例として、次のスクリプトを検討してください。

$ cat ~/w/bin/x
#! /bin/bash
cmd=$1; shift
case $1 in *%*) flt=${1//\%/\'%s\'};; *) flt="$1 '%s'";; esac; shift
q="'\\''"; f=${flt//\\/'\\'}; p=`printf "<($f) " "${@//\'/$q}"`
eval "$cmd" "$p"

あらすじ

x command filter [file ...]

filterfile引数に適用されcommand、フィルターの出力を引数として呼び出します。

例えば:

x diff zcat a.gz b.bz   # diff gzipped files
x diff3 zcat a.gz b.gz c.gz   # same with three-way diff
x diff hd a b  # hex diff of binary files
x diff 'zcat % | sort -u' a.gz b.gz  # first uncompress the files, then sort+uniq them, then compare them
x 'comm -12' sort a b  # find common lines in unsorted files

これは、Lispでできることに非常に近い(冗談です;-))

一部の人々は、「コールバック」の用語を「イベントハンドラ」および/または「クロージャ」(関数+データ/環境タプル)に限定することを主張しています。これは決して一般的に 受け入れられている意味ではありません。そして、これらの狭い意味での「コールバック」がシェルであまり役に立たない理由の1つは、パイプ+並列処理+動的プログラミング機能がはるかに強力であり、パフォーマンスの面で既に支払っているからです。perlまたはの不格好なバージョンとしてシェルを使用してみてくださいpython


あなたの例は非常に便利に見えますが、十分に密集しているので、bashマニュアルを開いて実際にどのように動作するかを理解する必要があります(そして何年もの間、より単純なbashを使用してきました)。 Lisp。;)
ジョー

1
@Joe 2つの入力ファイルのみを処理し%、フィルターの補間を行わない場合、全体を次のように減らすことができますcmd=$1; shift; flt=$1; shift; $cmd <($flt "$1") <($flt "$2")。しかし、それはあまり有用ではなく、例示的な私見です。
mosvy

1
あるいはさらに良い$1 <($2 "$3") <($2 "$4")
mosvy

+1ありがとう。あなたのコメントに加えて、それをじっと見つめ、しばらくの間コードをいじってみたところ、それが明らかになりました。また、私はこれまでずっと使用してきたものを表す「文字列補間」という新しい用語を学びました。
ジョー

4

やや。

bashでコールバックを実装する簡単な方法の1つは、「コールバック関数」として機能するパラメーターとしてプログラムの名前を受け入れることです。

# 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にはクロージャーはありません。したがって、コールバック関数は呼び出し側の変数にアクセスできません。ただし、コールバックに必要なデータを環境変数に保存できます。コールバックから呼び出し側スクリプトに情報を渡すことは、より複雑です。データをファイルに入れることができます。

設計上、すべてを単一のプロセスで処理できる場合、コールバックにシェル関数を使用できます。この場合、コールバック関数はもちろん、呼び出し側の変数にアクセスできます。


3

他の回答にいくつかの単語を追加するだけです。関数コールバックは、コールバックする関数の外部の関数で動作します。これを可能にするには、コールバックする関数の定義全体をコールバックする関数に渡すか、コールバックする関数でそのコードを使用できるようにする必要があります。

前者(コードを別の関数に渡す)が可能ですが、複雑さを伴う例についてはスキップします。後者(名前で関数を渡す)は一般的な方法です。1つの関数のスコープ外で宣言された変数と関数は、それらの定義がそれらを操作する関数の呼び出しに先行する限り、その関数で利用可能です(順番に、呼び出される前に宣言されるように)。

また、関数がエクスポートされるときに同様のことが起こることに注意してください。関数をインポートするシェルは、フレームワークの準備ができていて、関数定義が実行されるのを待っているだけかもしれません。関数のエクスポートはBashに存在し、以前は深刻な問題を引き起こしていました(btw(Shellshockと呼ばれていました)):

関数を別の関数に渡すもう1つの方法でこの答えを完成させます。これはBashには明示的に存在しません。これは名前ではなくアドレスで渡します。これは、たとえばPerlで見つけることができます。Bashは、関数にも変数にもこの方法を提供しません。しかし、あなたが述べているように、単なる例としてBashでより広い画像を持ちたい場合は、関数コードがメモリのどこかに存在し、そのコードはそのメモリの場所によってアクセスされる可能性があることを知っておく必要がありますそのアドレスと呼ばれます。


2

bashのコールバックの最も簡単な例の1つは、多くの人がよく知っているものの、実際に使用しているデザインパターンがわからないことです。

cron

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'

もちろん、24時間ごとの関数は戻りません。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

上記のフォームを見るとわかるように、コールバックが言語の直接的な機能になることはめったにありません。彼らは通常、既存の言語機能を使用して創造的な方法でプログラミングしています。ポインタ/参照/コードブロック/関数/スクリプトのコピーを保存できる言語であれば、コールバックを実装できます。


コールバックを受け入れるプログラム/スクリプトのその他の例には、watchand find-execパラメーターで使用する場合)
-slebetman

0

コールバックは、何らかのイベントが発生したときに呼び出される関数です。を使用すると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

次に、1つのターミナルでスクリプトを実行します。

$ ./callback-example

そして別のものでUSR1、シェルプロセスにシグナルを送信します。

$ pkill -USR1 callback-example

送信される各信号は、最初の端末で次のような行の表示をトリガーする必要があります。

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

ksh93bash後に採用される多くの機能を実装するシェルとして、「規律機能」と呼ばれるものを提供します。これらの関数は、では使用できませんがbash、シェル変数が変更または参照(読み取り)されるときに呼び出されます。これにより、より興味深いイベント駆動型アプリケーションへの道が開かれます。

たとえば、この機能により、グラフィックウィジェットでのX11 / Xt / Motifスタイルのコールバックを、kshと呼ばれるグラフィック拡張機能を含む古いバージョンに実装できましたdtkshdkshのマニュアルを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.