Linuxでシェル関数を見つける-exec?


185

findシェルで定義した関数を実行する方法はありますか?例えば:

dosomething () {
  echo "doing something with $1"
}
find . -exec dosomething {} \;

その結果は次のとおりです。

find: dosomething: No such file or directory

取得する方法があるfindのを-exec見るためにはdosomething

回答:


262

シェルだけがシェル関数の実行方法を知っているため、関数を実行するにはシェルを実行する必要があります。また、エクスポートする関数をでマークする必要がありますexport -f。そうしないと、サブシェルがそれらを継承しません。

export -f dosomething
find . -exec bash -c 'dosomething "$0"' {} \;

7
あなたは私を倒しました。ちなみに、を使用する代わりに、中括弧を引用符で囲むことができます$0
追って通知があるまで一時停止。

20
使用find . -exec bash -c 'dosomething "$0"' {} \;する場合、ファイル名のスペース(およびその他の奇妙な文字)を処理します...
Gordon Davisson

3
@alxndr:二重引用符、逆引用符、ドル記号、いくつかのエスケープコンボなどのあるファイル名では失敗します...
Gordon Davisson

4
また、関数が呼び出している可能性のある関数は、それらも-fをエクスポートしない限り使用できません。
hraban 2016年

3
export -fbashの一部のバージョンでのみ動作します。それはcrossplatfornではなく、POSIXではないですが、/bin/shそれとの誤差があります
РоманКоптев

120
find . | while read file; do dosomething "$file"; done

10
素晴らしい解決策。関数をエクスポートしたり、エスケープ引数をいじったりする必要はなく、各関数を実行するためにサブシェルを生成しないため、おそらくより効率的です。
トム

19
ただし、改行を含むファイル名では機能しないことに注意してください。
chepner 2013

3
毎回完全に新しいシェル/環境を作成することなく、グローバル変数と関数を使用できるため、これはより「シェルっぽい」です。アダムの方法を試し、あらゆる種類の環境問題に遭遇した後、これを一生懸命学びました。この方法でも、現在のユーザーのシェルがすべてのエクスポートで破損することはなく、必要な規律も低くなります。
Ray Foss

サブシェルなしで受け入れられた回答よりもはるかに高速です!いいね!ありがとうございました!
ビル・ヘラー

2
またwhile read、forループを変更して問題を修正しました。for item in $(find . ); do some_function "${item}"; done
user5359531 2018年

22

上記のJakの答えはすばらしいですが、簡単に克服できるいくつかの落とし穴があります。

find . -print0 | while IFS= read -r -d '' file; do dosomething "$file"; done

これは、改行ではなくnullを区切り文字として使用するため、改行付きのファイル名が機能します。また-r、バックスラッシュのエスケープを無効にするフラグを使用します。ファイル名にバックスラッシュがないと機能しません。またIFS、名前の末尾にある可能性のある空白が破棄されないようにクリアされます。


1
には適して/bin/bashいますが、では機能しません/bin/sh。残念です。
РоманКоптев

@РоманКоптев幸運なことに、少なくとも/ bin / bashで動作します。
スデナム

21

{}以下に示すように引用符を追加します。

export -f dosomething
find . -exec bash -c 'dosomething "{}"' \;

これによりfind、たとえば名前に括弧が含まれるファイルなど、によって返される特殊文字によるエラーが修正されます。


1
これは正しい使い方ではありません{}。これは、二重引用符を含むファイル名で壊れます。touch '"; rm -rf .; echo "I deleted all you files, haha。おっとっと。
gniourf_gniourf 2014年

はい、これは非常に悪いです。注射によって悪用される可能性があります。非常に危険です。これは使用しないでください。
ドミニク2016年

1
@kdubs:コマンド文字列内で$ 0(引用符なし)を使用し、最初の引数としてファイル名を渡します:-exec bash -c 'echo $0' '{}' \;を使用する場合bash -c、$ 0はスクリプト名ではなく最初の引数です。
スデナム

10

結果をまとめて処理する

効率を上げるために、xargs結果を大量に処理するために多くの人々が使用しますが、それは非常に危険です。そのため、find結果を一括で実行する代替メソッドが導入されました。

この方法はPOSIX-における要件例えばのようないくつかの警告と来るかもしれないことしかし注意find持っている{}コマンドの最後に。

export -f dosomething
find . -exec bash -c 'for f; do dosomething "$f"; done' _ {} +

findは、多くの結果を引数としての単一の呼び出しに渡しbashfor-loopはそれらの引数を反復処理してdosomething、それぞれの関数を実行します。

上記のソリューションは引数をで開始します。その$1ため、_(を表す$0)があります。

結果を1つずつ処理する

同様に、受け入れられたトップアンサーを修正して、

export -f dosomething
find . -exec bash -c 'dosomething "$1"' _ {} \;

引数は常にで始まる必要があるため、これはより健全なだけでなく、によって返されるファイル名がシェルにとって特別な意味を持つ場合、$1使用$0すると予期しない動作が発生する可能性findがあります。


9

見つかった各項目を引数として渡して、スクリプト自体を呼び出します。

#!/bin/bash

if [ ! $1 == "" ] ; then
   echo "doing something with $1"
   exit 0
fi

find . -exec $0 {} \;

exit 0

スクリプトを単独で実行すると、探しているものが見つかり、検索結果を引数として渡して自分自身を呼び出します。スクリプトを引数付きで実行すると、その引数に対してコマンドが実行されてから終了します。


クールなアイデアですが、スタイルが悪いです。2つの目的で同じスクリプトを使用します。bin /内のファイルの数を減らしたい場合は、すべてのスクリプトを1つのスクリプトにマージして、最初に大きな大文字句を含めることができます。とてもきれいな解決法ですね。
user829755 2017年

... find: ‘myscript.sh’: No such file or directoryとして開始した場合、これは失敗することは言うまでもありませんbash myscript.sh
Camusensei

5

現在のディレクトリ内のすべてのファイルに対して特定のコマンドを実行するbash関数を探している方のために、上記の回答から1つをコンパイルしました。

toall(){
    find . -type f | while read file; do "$1" "$file"; done
}

スペースを含むファイル名で壊れることに注意してください(以下を参照)。

例として、次の関数を見てください。

world(){
    sed -i 's_hello_world_g' "$1"
}

現在のディレクトリのすべてのファイルで、helloのすべてのインスタンスをworldに変更したいとします。私はします:

toall world

ファイル名のシンボルを安全にするには、次を使用します。

toall(){
    find . -type f -print0 | while IFS= read -r -d '' file; do "$1" "$file"; done
}

(ただし、GNUなどfindを処理するが必要です)。-print0find


3

私は次のように最も簡単な方法を見つけます、単一のdoで2つのコマンドを繰り返す

func_one () {
  echo "first thing with $1"
}

func_two () {
  echo "second thing with $1"
}

find . -type f | while read file; do func_one $file; func_two $file; done

3

参考までに、私はこのシナリオを回避します:

for i in $(find $dir -type f -name "$name" -exec ls {} \;); do
  _script_function_call $i;
done;

現在のスクリプトファイルでfindの出力を取得し、必要に応じて出力を反復処理します。受け入れられた回答に同意しますが、スクリプトファイルの外部で関数を公開したくありません。


これにはサイズ制限があります
Richard

2

その方法で関数を実行することはできません。

これを克服するには、関数をシェルスクリプトに配置して、 find

# dosomething.sh
dosomething () {
  echo "doing something with $1"
}
dosomething $1

次に、次のように検索で使用します。

find . -exec dosomething.sh {} \;

〜/ binにあるファイルが増えるのを避けようとしていました。ありがとう!
alxndr

私は反対投票を検討しましたが、解決策自体は悪くありません。正しい引用を使用してください:dosomething $1=> dosomething "$1"でファイルを正しく開始してくださいfind . -exec bash dosomething.sh {} \;
Camusensei

2

関数を別のファイルに入れ、findそれを実行します。

シェル関数は、それらが定義されているシェルの内部にあります。findそれらを見ることができなくなります。


ゴッチャ; 理にかなっています。私の〜/ binでより多くのファイルを避けようとしていましたが。
alxndr

2

execまたはexecdir-exec command {} +)の一括オプションを使用していて、すべての位置引数を取得したい場合、他のいくつかの回答に追加と説明を提供するには、$0with の処理を考慮する必要がありますbash -c。より具体的には、bash -c上記のように使用し、見つかった各ディレクトリから '.wav'で終わるファイルパスをエコー出力する以下のコマンドを検討してください。

find "$1" -name '*.wav' -execdir bash -c 'echo $@' _ {} +

bashマニュアルにはこう書かれています:

If the -c option is present, then commands are read from the first non-option argument command_string.  If there are arguments after the command_string, they  are  assigned  to  the
                 positional parameters, starting with $0.

ここで、'check $@'はコマンド文字列であり_ {}、コマンド文字列の後の引数です。$@はbashの特別な定位置パラメーターであり、1から始まるすべての定位置パラメーターに展開されることに注意してください。また、この-cオプションを使用すると、最初の引数が位置パラメータに割り当てられることに注意してください$0。つまり、を使用してすべての位置パラメータにアクセスしようとすると$@、最初から上方向のパラメータのみが取得されます$1。これが、ドミニクの回答に、つまり_パラメーターを埋めるための仮引数であるがあるため、たとえばパラメーター拡張やその回答のforループ$0を使用すると、必要なすべての引数を後で使用できるようになり$@ます。

もちろん、受け入れられた回答と同様に、bash -c 'shell_function $0 $@'明示的にを渡すことによっても機能し$0ますが$@、期待どおりに機能しないことを覚えておく必要があります。


0

直接ではありません。Findはシェルではなく、別のプロセスで実行されています。

関数と同じ働きをするシェルスクリプトを作成し、それを見つけることができ-execます。


〜/ binにあるファイルが増えるのを避けようとしていました。ありがとう!
alxndr

-2

-execまったく使用しないようにします。なぜ使用しないのxargsですか?

find . -name <script/command you're searching for> | xargs bash -c

当時、IIRCは使用するリソースの量を減らすことを試みていました。何百万もの空のファイルを見つけて削除することを考えてください。
alxndr 14
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.