質問1:

質問1:

マニュアルページを読んでいますが、find次のコマンドについて混乱しました。一つと対応するものの違いは何ですか?

  1. 次の2つのコマンドの違いは何ですか?

    find -execdir command "{}" \;
    find -execdir "command {}" \;
    

混乱の理由:引用は、シェルが引用した部分をブロックとして処理するように指示しなければならないと考えました。それで、2番目のものを見たときに命令がなく、失敗することがわかりましたcommand <file-name>

  1. 次の2つの違いは何ですか?

    find -execdir bash -c "command" "{}" \;
    find -execdir bash -c "command {}" \;
    

混乱の理由:私が理解しているように、2番目のバージョンでは、コマンドは中括弧で囲まれ、bashコマンド全体に渡されるべきであり、中find括弧はそのファイル名として解釈されるべきではありません。

  1. 次の2つの違いは何ですか?

    find -execdir bash -c "something \"$@\"" {} \;
    find -execdir bash -c 'something "$@"' bash {} \;
    

私が理解する限り、両方とも同じです。中かっこをシェルに渡す方法は、2番目のバージョンが最初のバージョンよりも安全です。

修正する

質問#3のコマンドの最初のバージョンが機能しないことを発見しました!以下を試しました(誰も動作しませんでした)。

find -execdir bash -c 'something \"$@\"' {} \; # changed external quotes to single ones.
find -execdir bash -c "something $@" {} \; # removed the inner quotes.
find -execdir bash -c 'something $@' {} \; # same as above but with outer single quotes instead.

私がここで何を見逃しているのでしょうか?コマンド引用符の外側に中かっこを入れることができると思います。

答え1

これは(あなたが知っているように)非常に複雑です。私はそれを説明しようとします。コマンドが実行する解析/処理の順序を考えて、各ステップで何が起こるのかを観察するのが役立ちます。通常、プロセスは次のようになります。

  1. シェルはコマンドラインを解析してトークン(「単語」)に分割し、変数参照などを置き換え、引用符とエスケープを削除します(対応する効果を適用した後)。次に(通常)最初の「単語」をコマンド名(この場合は「find」)として実行し、残りの単語を引数として渡します。
  2. findファイルを検索し、「-execdir」と「」の間;の内容をコマンドとして実行します。 " " を一致するファイル名に変更します{}が、他の解析は実行しません。-execdirコマンド名として""の後に最初の引数を実行し、次の引数を渡します。それ議論。
  3. コマンドが発生してオプションが渡されるbashと、-c次の引数を-cコマンド文字列(ある種のミニシェルスクリプトに似ています)として解析し、残りの引数をそのミニスクリプトの引数として解析します。

さて、さらに進む前にいくつかの追加事項を教えてください。私はBSDを使用していますがfind、検索するディレクトリを明示的に指定する必要があるため、find . -execdir ...単にを使用するのではなく、find -execdir ...私のディレクトリに "foo.txt"ファイルが含まれています。 jpg」(間違った使用のリスクを説明するためbash -c)、最後に、pargs引数を印刷する(何も得られないと文句を言う)バイナリディレクトリの短いスクリプトを呼び出しました。

質問1:

それでは、最初の質問の2つのコマンドを試してみましょう。

$ find . -execdir pargs "{}" \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^;*;echo wheee.jpg'
$ find . -execdir "pargs {}" \;
find: pargs .: No such file or directory
find: pargs foo.txt: No such file or directory
find: pargs z@$%^;*;echo wheee.jpg: No such file or directory

これは期待どおりに行われます。最初は機能しますが(btw、周囲の二重引用符を省略できます{})、2番目は失敗します。スペースとファイル名は、引数ではなくコマンド名の一部と見なされるためです。

-exec[dir] ... +ただし、-thisを使用してできるだけ少ない数のコマンドでコマンドを実行し、一度に複数のファイル名を渡すように指示する-exec[dir] \;こともできます。find

$ find . -execdir pargs {} +
pargs got 3 argument(s): '.' 'foo.txt' 'z@$%^;*;echo wheee.jpg'

質問2:

今回は一度に1つのオプションを選択します。

$ find . -execdir bash -c "pargs" "{}" \;
pargs didn't get any arguments
pargs didn't get any arguments
pargs didn't get any arguments

「うーん」って?ここで何が起こっているのかは、bash""、" "、" "などのパラメータリストを使用して実行されることです。このオプションは、次のようにミニシェルスクリプトのように次の引数( "pargs")を実行するように指示します。-cpargsfoo.txt-cbash

#!/bin/bash
pargs

...そして何とか「ミニスクリプト」パラメータ「foo.txt」を渡します(詳細は後述)。しかし、ミニスクリプトは引数に対して何もしません。特に引数をコマンドに渡さないので、pargspargsも表示されません。 (これを行う正しい方法については、3番目の質問で見つけてみましょう。)次に、2番目の質問の2番目の選択肢を試してみましょう。

$ find . -execdir bash -c "pargs {}" \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^'
bash: foo.txt: command not found
wheee.jpg

現在、すべてが機能していますが、部分的にのみ機能します。bash引数" -c"と"pargs" +ファイル名を使用して実行すると、"."で期待どおりに機能します。そして "foo.txt"ですが、" "と " "パラメータbashを渡すと、次のミニスクリプトが実行されます。-cpargs z@$%^;*;echo wheee.jpg

#!/bin/bash
pargs z@$%^;*;echo wheee.jpg

したがって、bashはこれをセミコロンで区切られた3つのコマンドに分割します。

  1. pargs z@$%^」(あなたが見る効果)
  2. ""は " "と " "という*単語に展開されているので、コマンドで実行して別のファイル名を引数として渡してみてください。その名前のコマンドがないため、適切なエラーが発生します。foo.txtz@$%^;*;echo wheee.jpgfoo.txt
  3. echo echo wheee.jpg」、端末に「wheee.jpg」を印刷するのがわかるように、これは完全に合理的なコマンドです。

したがって、一般名を持つファイルでは機能しますが、シェル構文を含むファイル名が見つかると、ファイル名の一部を実行しようとします。そのため、この方法は安全ではありません。

質問3:

もう一度、1つのオプションを見てみましょう。

$ find . -execdir bash -c "pargs \"$@\"" {} \;
pargs got 1 argument(s): ''
pargs got 1 argument(s): ''
pargs got 1 argument(s): ''
$ 

もう一度「うん?」と言うのを聞きました。ここで最大の問題は、$@エスケープしたり一重引用符で囲まれたりせず、現在のシェルコンテキストによって拡張されることです。今後に渡されました。ここで実際に得られたパラメータを表示するfindのに使います。pargsfind

$ pargs . -execdir bash -c "pargs \"$@\"" {} \;
pargs got 7 argument(s): '.' '-execdir' 'bash' '-c' 'pargs ""' '{}' ';'

$@インタラクティブシェルで実行されていて引数を受け取っていないため(またはコマンドを使用して設定しなかったため、ただ消えました。set)だから、私たちは次のミニスクリプトを実行しています。

#!/bin/bash
pargs ""

...pargs空の引数を取得する理由を説明します。

これがスクリプトにあった場合持つ引数を受け取ると、状況はさらに混乱します。エスケープ(または一重引用符)を使用すると$問題は解決しますが、それでも正しく機能しません。

$ find . -execdir bash -c 'pargs "$@"' {} \;
pargs didn't get any arguments
pargs didn't get any arguments
pargs didn't get any arguments

ここでの問題は、bashミニスクリプトの後にある次のパラメータがミニスクリプトの名前で処理されることです(ミニスクリプトはとして使用できますが$0いいえ$@通常の引数(例:)ではなく)に含まれます$1。以下は、この機能を示す一般的なスクリプトです。

$ cat argdemo.sh 
#!/bin/bash
echo "My name is $0; I received these arguments: $@"
$ ./argdemo.sh foo bar baz
My name is ./argdemo.sh; I received these arguments: foo bar baz

さて、似たようなbash -cミニスクリプトを試してみてください。

$ bash -c 'echo "My name is $0; I received these arguments: $@"' foo bar baz
My name is foo; I received these arguments: bar baz

この問題を解決する標準的な方法は、実際のパラメータが一般的な方法で表示されるようにダミースクリプト名パラメータ(「bash」など)を追加することです。

$ bash -c 'echo "My name is $0; I received these arguments: $@"' mini-script foo bar baz
My name is mini-script; I received these arguments: foo bar baz

これがまさに2番目のオプションが行うことです。 「bash」をスクリプト名として渡し、見つかったファイル名を次のように渡します$1

$ find . -execdir bash -c 'pargs "$@"' bash {} \;
pargs got 1 argument(s): '.'
pargs got 1 argument(s): 'foo.txt'
pargs got 1 argument(s): 'z@$%^;*;echo wheee.jpg'

結局、奇妙なファイル名を使用しても動作しました。これが、このオプション(または最初の質問の最初のオプション)が使用されると見なされる理由ですfind -exec[dir]-exec[dir] ... +以下と組み合わせて使用​​することもできます。

$ find . -execdir bash -c 'pargs "$@"' bash {} +
pargs got 3 argument(s): '.' 'foo.txt' 'z@$%^;*;echo wheee.jpg'

関連情報