find -exec呼び出しでカスタム関数を実行する

find -exec呼び出しでカスタム関数を実行する

私はSolaris 10を使用しており、ksh(88)、bash(3.00)、およびzsh(4.2.1)を使用して以下をテストしました。

次のコードは結果を生成しません。

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

findは複数のファイルと一致し(代替として表示されます-exec ...)、-print関数はfind外部から呼び出されると完全に機能します。

man findこのページの内容は次のとおりです-exec

-exec コマンド 実行されたコマンドが以下を返すと真です。
                     0 値は終了状態として使用されます。終わり
                     コマンドはエスケープ文字で区切る必要があります。
                     セミコロン(;)。コマンドパラメータ{}は
                     現在のパス名に置き換えます。もし
                     -execの最後の引数は{}です。
                     セミコロン(;)の代わりに+を指定してください。
                     コマンドはあまり頻繁に呼び出されません。
                     {} はパス名グループに置き換えられます。もし
                     このコマンドを呼び出すと返されます。
                     終了状態でゼロ以外の値を探します。
                     ゼロ以外の終了状態を返します。

おそらく次のようにすることができます。

for f in $(find somedir); do
    foo
done

しかし、フィールド区切り記号の問題に対処するのは怖いです。

呼び出しでシェル関数を呼び出すことは可能ですか(同じスクリプトで定義されているので、スコープの問題を心配する必要はありません)find ... -exec ...

私は両方とも試してみましたが、/usr/bin/find同じ/bin/find結果を得ました。

答え1

Afunctionはシェル固有の関数なので、find -exec使用する前にシェルを作成し、そのシェルで関数を定義する必要があります。それは次のとおりです。

find ... -exec ksh -c '
  function foo {
    echo blan: "$@"
  }
  foo "$@"' ksh {} +

bash環境を介して機能をエクスポートできるのでexport -f(bashで)、次のことができます。

foo() { ...; }
export -f foo
find ... -exec bash -c 'foo "$@"' bash {} +

ksh88この関数は(環境を介さずに)エクスポートする必要がありますtypeset -fxが、実行中のシェーバンレススクリプトでのみ使用できるため、ksh使用できませんksh -c

別のオプションは、次のことです。

find ... -exec ksh -c "
  $(typeset -f foo)"'
  foo "$@"' ksh {} +

つまり、インラインスクリプトから関数typeset -f定義をダンプするために使用されます。他の機能を使用している場合は、その機能もダンプする必要があります。foofoo

ps -fあるいは、コマンドラインではなく環境変数を介して関数定義を渡すこともできます(出力に表示されます)。

FUNCDEFS=$(typeset -f foo) find ... -exec ksh -c '
  eval "$FUNCDEFS" &&
    unset -v FUNCDEFS &&
    foo "$@"' ksh {} +

unset -v FUNCDEFSこの機能で実行されるコマンドの環境を汚染しないようにfoo(存在する場合))

答え2

\ 0を区切り文字として使用し、生成されたコマンドのファイル名を次のように現在のプロセスに読み込みます。

foo() {
  printf "Hello {%s}\n" "$1"
}

while IFS= read -d '' -r filename; do
  foo "${filename}" </dev/null
done < <(find . -maxdepth 2 -type f -print0)

ここで何が起こっていますか?

  • read -d ''次の \0 バイトまで読み取るので、ファイル名の奇妙な文字について心配する必要はありません。
  • 同様に-print0、生成された各ファイル名を終了するには、\ nの代わりに\ 0を使用します。
  • オプション-r(raw)は、ファイル名からバックスラッシュがエスケープされるのを防ぎます。
  • cmd2 < <(cmd1)cmd2と同じですcmd1 | cmd2が、cmd2はサブシェルの代わりにプライマリシェルで実行されます。
  • fooへの呼び出しは、/dev/null誤ってパイプから読み込まれないようにリダイレクトされます。
  • $filename引用されているため、シェルはスペースを含むファイル名を分割しようとしません。
  • IFS=Bashがファイル名から末尾のスペースを削除するのを防ぎます(ありがとうダニエル・スメドガードブースこの修正のために)

zsh、bash、ksh 93uread -dでは、<(...)以前のバージョンのkshがわかりません。

答え3

事前定義されたシェル機能を使用するためにスクリプトによって生成されたサブプロセスが必要な場合は、それをエクスポートする必要があります。export -f <function>

注:export -fbashのみ

ただ一つだけだからシェル実行できますシェル機能:

find / -exec /bin/bash -c 'function "$1"' bash {} \;

編集:デフォルトでは、スクリプトは次のようになります。

#!/bin/bash
function foo() { do something; }
export -f foo
find somedir -exec /bin/bash -c 'foo "$0"' {} \;

答え4

常に適用されるわけではありませんが、適用される場合は簡単な解決策です。このglobstarオプションを設定します(set -o globstarksh93ではshopt -s globstarbash ≥4、zshではデフォルトで有効になっています)。次に、再帰を使用して**/現在のディレクトリとそのサブディレクトリを一致させます。

たとえば、find . -name '*.txt' -exec somecommand {} \;次の代わりに実行できます。

for x in **/*.txt; do somecommand "$x"; done

代わりに、find . -type d -exec somecommand {} \;以下を実行できます。

for d in **/*/; do somecommand "$d"; done

代わりに、find . -newer somefile -exec somecommand {} \;以下を実行できます。

for x in **/*; do
  [[ $x -nt somefile ]] || continue
  somecommand "$x"
done

これが機能しない場合**/(シェルに対応する機能がないか、findシェルアナログを持たないオプションが必要なため)、find -execパラメータで関数を定義する

関連情報