web-dev-qa-db-ja.com

find -exec呼び出しでユーザー定義関数を実行する

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

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

function foo {
    echo "Hello World"
}

find somedir -exec foo \;

検索はいくつかのファイルと一致し(-exec ...-printで置き換えることで示されているように)、関数はfind呼び出しから外部で呼び出されたときに完全に機能します。

man findページの-execについてのコメントは次のとおりです:

 -exec command実行されたコマンドが終了ステータスとして
のゼロ値を返す場合は真。 
コマンドの終わりは、エスケープされた
セミコロン(;)で区切る必要があります。コマンド引数{}は
で現在のパス名に置き換えられます。 -execの
の最後の引数が{}であり、
にセミコロン(;)ではなく+を指定すると、
コマンドは、
 {}はパス名のグループに置き換えられました。 
コマンドの呼び出しで終了ステータスとして
ゼロ以外の値が返される場合、find 
はゼロ以外の終了ステータスを返します。

私はおそらくこのようなことをして逃げることができました:

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

しかし、フィールドセパレータの問題に対処することを恐れています。

find ... -exec ...呼び出しからシェル関数(同じスクリプトで定義され、スコープの問題に悩まないでください)を呼び出すことはできますか?

/usr/bin/find/bin/findの両方で試したところ、同じ結果が得られました。

28
rahmu

functionはシェルに対してローカルであるため、シェルを生成してその関数をそのシェルで定義してから使用できるようにするには、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を使用して、インラインスクリプト内のfoo関数の定義をダンプします。 fooが他の関数を使用する場合、それらもダンプする必要があることに注意してください。

28

これは常に当てはまるわけではありませんが、当てはまる場合、それは簡単な解決策です。 globstarオプションを設定します(ksh93では_set -o globstar_、bash≥4では_shopt -s globstar_。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_引数 の関数。

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

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

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

何が起きてる:

  • _read -d ''_は次の\ 0バイトまで読み取るので、ファイル名の奇妙な文字について心配する必要はありません。
  • 同様に、_-print0_は\ nの代わりに\ 0を使用して、生成された各ファイル名を終了します。
  • cmd2 < <(cmd1)は、cmd2がサブシェルではなくメインシェルで実行されることを除いて、_cmd1 | cmd2_と同じです。
  • fooの呼び出しは_/dev/null_からリダイレクトされ、誤ってパイプから読み取られないようにします。
  • _$filename_が引用されているため、シェルは空白を含むファイル名を分割しようとしません。

現在、_read -d_と<(...)はzsh、bash、ksh 93uにありますが、以前のkshバージョンについてはよくわかりません。

5
aecolley

スクリプトから生成された子プロセスに、事前定義されたシェル関数を使用させる場合は、export -f <function>でエクスポートする必要があります。

注:export -fはbash固有です

ShellのみShell関数を実行できるため、

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
h3rrmiller