Bashに「コールバック」というプログラミングの概念はありますか?

Bashに「コールバック」というプログラミングの概念はありますか?

プログラミングについて読んで、何度も「コールバック」という概念に触れました。

興味深いことに、私は「コールバック関数」という用語について「教訓的」または「明確」と呼ぶことができる説明を一度も見つけることができませんでした(私が読んだほとんどすべての説明は他の説明とは非常に異なるようです)。混乱)。

Bashに「コールバック」というプログラミングの概念はありますか?それでは、小さくて簡単なBashの例に答えてください。

答え1

典型的な命令型プログラミングでは、明示的な制御フローを通じて順番に実行される一連のコマンドを作成します。たとえば、

if [ -f file1 ]; then   # If file1 exists ...
    cp file1 file2      # ... create file2 as a copy of a file1
fi

など。

例に示すように、命令型プログラミングでは、実行フローに非常に簡単に従うことができます。常に与えられたコード行から始まり、実行コンテキストを決定し、ユーザーが提供するすべてのコマンドが実装されることがわかります。プロセスの場所(または関数を作成する場合は呼び出しサイトの場所)。

コールバックがフローを変更する方法

コールバックを使用する場合、コマンドセットの使用を「地理的」に配置するのではなく、呼び出す必要がある時期を説明することです。他のプログラミング環境の一般的な例には、「このリソースをダウンロードし、ダウンロードが完了したらこのコールバックを呼び出す」などの状況があります。 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番目の例で説明したユースケースでは、「コールバック」という用語を直接使用しませんが、これは私が使用している言語からの習慣である可能性があります。関数やラムダを渡すと思います。 )。

答え2

まず、機能が何であるかではなく、使用方法に応じて関数がコールバック関数になることに注意することが重要です。コールバックは、作成していないコードから作成したコードを呼び出すことです。特定のイベントが発生した場合は、システムに電話をかけるように要求します。

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

コールバックの別の例は、コマンド-execの操作ですfind。このコマンドが行うことは、findディレクトリを繰り返し繰り返し、各ファイルを順番に処理することです。デフォルトでは、処理はファイル名を印刷すること(暗黙的に-print)ですが、処理-execはユーザーが指定したコマンドを実行することです。これはコールバックの定義に適していますが、コールバックは別のプロセスで実行されるため、あまり柔軟ではありません。

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
}

シェルは主に単純なプログラム用に設計されているため、シェルプログラミングでは他の環境と同様にコールバックは一般的ではありません。コールバックは、データと制御フローが独立して作成されたコード部分と、デプロイされたコード部分(基本システム、さまざまなライブラリ、アプリケーションコード)との間を行き来する傾向がある環境でより一般的です。

答え3

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

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

簡単な例として、次のスクリプトを見てみましょう。

$ 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 ...]

filter各引数に適用された次のfileフィルタ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つは、パイプライン+並列性+動的プログラミングがはるかに強力であり、シェルはperlorの薄暗いバージョンですpython

答え4

他の答えにいくつかの単語を追加するだけです。関数コールバックは、コールバック関数の外側の関数で機能します。これを達成するには、コールバックする関数の完全な定義をコールバック関数に渡すか、そのコードをコールバック関数で使用できる必要があります。

前者(他の関数にコードを渡す)も可能ですが、複雑な問題のために例をスキップします。後者(名前で関数を渡す)は一般的な慣行です。関数の範囲外で宣言された変数と関数は、定義がその関数への呼び出しの前にある限り、その関数で使用できるためです(逆に、前に宣言されています)。呼ぶ)。

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

Bashで明示的に提供されていない他の関数に関数を渡す別の方法でこの答えを完了します。このメソッドは、名前ではなくアドレスに渡されます。この方法は、例えばPerlにあります。 Bashは関数や変数に対してこの方法を提供しません。ただし、Bashの例を使用して状況をより完全に理解するには、関数コードがメモリのどこかにある可能性があり、そのメモリ位置(アドレスと呼ばれる)からアクセスできることに注意する必要があります。コード。

関連情報