ただやるだけでは効果がない

ただやるだけでは効果がない

まず、些細だが適用できない回答をカットします。呼び出しごとにそのような式をほとんど使用しない必要があるため、 find+xargsトリックもそのバリエーション ( などfind)も使用できません-exec。最後にこれに戻ります。


より良い例として、次のことを考えてみましょう。

$ find -L some/dir -name \*.abc | sort
some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

これらを引数として渡すにはどうすればよいですかprogram?

ただやるだけでは効果がない

$ ./program $(find -L some/dir -name \*.abc | sort)

次の引数を取得するため失敗しますprogram:

[0]: ./program
[1]: some/dir/1.abc
[2]: some/dir/2.abc
[3]: some/dir/a
[4]: space.abc

ご覧のとおり、スペースを含むパスは分割され、program2 つの異なる引数として扱われます。

うまくいくまで見積もります

私のような初心者ユーザーは、このような問題に直面したとき、最終的にうまくいくまでランダムに引用符を追加する傾向があるようですが、ここでだけは役に立たないようです...

"$(…)"

$ ./program "$(find -L some/dir -name \*.abc | sort)"
[0]: ./program
[1]: some/dir/1.abc
some/dir/2.abc
some/dir/a space.abc

引用符によって単語の分割が防止されるため、すべてのファイルが 1 つの引数として渡されます。

個々のパスを引用する

有望なアプローチ:

$ ./program $(find -L some/dir -name \*.abc -printf '"%p"\n' | sort)
[1]: "some/dir/1.abc"
[2]: "some/dir/2.abc"
[3]: "some/dir/a
[4]: space.abc"

確かに引用符は存在します。しかし、引用符はもはや解釈されません。引用符は文字列の一部にすぎません。したがって、引用符は単語の分割を防止しないだけでなく、議論の的にもなります。

IFSの変更

それから、 をいじってみました。とをどちらにしてもIFS使用したいです。そうすれば、それら自体に「ワイヤード パス」の問題がなくなるからです。 では、文字に対して単語分割を強制して、すべてを実現してみてはいかがでしょうか。find-print0sort-znull

$ ./program $(IFS=$'\0' find -L some/dir -name \*.abc -print0 | sort -z)
[0]: ./program
[1]: some/dir/1.abcsome/dir/2.abcsome/dir/a
[2]: space.abc

したがって、依然としてスペースで分割されますが、 では分割されませんnull

私は代入を(上記のように) と の前のIFS両方に配置しようとしました。また、、 、および で引用符で囲んだ、の有無にかかわらずなどの他の構文も試しました。どれも違いがないように見えました…$(…)./program\0\x0\x00'"$


そして、もうアイデアが尽きました。さらにいくつか試してみましたが、すべてリストにあるのと同じ問題に行き着くようです。

他に何ができるでしょうか? そもそも実行可能でしょうか?

もちろん、programパターンを受け入れて検索自体を実行するようにすることもできます。しかし、特定の構文に固定すると、二重の作業が必要になります。(たとえば、 でファイルを提供する場合はどうでしょうかgrep?)。

また、programパスのリストを含むファイルを受け入れるようにすることもできます。そうすれば、find式を一時ファイルに簡単にダンプし、そのファイルへのパスのみを提供できます。これは直接パスに沿ってサポートできるため、ユーザーが単純なパスのみを持っている場合は、中間ファイルなしで提供できます。しかし、これはあまり良い方法ではありません。追加のファイルを作成して管理する必要があり、追加の実装が必要になることは言うまでもありません。(ただし、プラス面としては、引数として指定したファイルの数によってコマンド ラインの長さに問題が生じ始めた場合の救済策になる可能性があります...)


最後に、find+ xargs(および同様の) トリックは私のケースでは機能しないことを再度お知らせします。説明を簡単にするために、1 つの引数のみを示します。ただし、実際のケースは次のようになります。

$ ABC_FILES=$(find -L some/dir -name \*.abc | sort)
$ XYZ_FILES=$(find -L other/dir -name \*.xyz | sort)
$ ./program --abc-files $ABC_FILES --xyz-files $XYZ_FILES

つまり、xargs1 つの検索を実行しても、もう 1 つの検索をどう処理するかがまだ残っています...

答え1

配列を使用します。

ファイル名に改行文字が含まれる可能性を考慮したくない場合は、次のようにすればいいでしょう。

mapfile -t ABC_FILES < <(find -L some/dir -name \*.abc | sort)
mapfile -t XYZ_FILES < <(find -L other/dir -name \*.xyz | sort)

それから

./program --abc-files "${ABC_FILES[@]}" --xyz-files "${XYZ_FILES[@]}"

もし、あんたがするファイル名内の改行を処理する必要があり、bash >= 4.4 を使用している場合は、配列の構築中に名前を null で終了するために-print0と を使用できます。-d ''

mapfile -td '' ABC_FILES < <(find -L some/dir -name \*.abc -print0 | sort -z)

(も同様ですXYZ_FILES)。しない新しいbashをお持ちの場合は、ヌル終端の読み取りループを使用して、配列にファイル名を追加できます。例:

ABC_FILES=()
while IFS= read -rd '' f; do ABC_FILES+=( "$f" ); done < <(find -L some/dir -name \*.abc -print0 | sort -z)

答え2

IFS=newline を使用できます (ファイル名に改行が含まれていないことを前提とします) が、置換の前に外部シェルで設定する必要があります。

$ ls -1
a file with spaces
able
alpha
baker
boo hoo hoo
bravo
$ # note semicolon here; it's not enough to be in the environment passed
$ # to printf, it must be in the environment OF THE SHELL WHILE PARSING
$ IFS=$'\n'; printf '%s\n' --afiles $(find . -name 'a*') --bfiles $(find . -name 'b*')
--afiles
./able
./a file with spaces
./alpha
--bfiles
./bravo
./boo hoo hoo
./baker

ではnullも使用できますが、では使用されませzshん。bash$'\0'bash

 IFS=$'\1'; ... $(find ... -print0 | tr '\0' '\1') ...

ただし、この方法では、@steeldriver の回答に対するコメントで行われた、find a が空の場合に --afiles を省略するという追加の要求は処理されません。

答え3

なぜ を諦めたのかよく分かりませんxargs

つまり、xargs1 つの検索を実行しても、もう 1 つの検索をどう処理するかがまだ残っています...

文字列は--xyz-files多くの引数のうちの 1 つに過ぎず、プログラムによって解釈される前にそれを特別なものと見なす理由はありません。xargs両方のfind結果の間でそれを渡すことができると思います。

{ find -L some/dir -name \*.abc -print0 | sort -z; echo -ne "--xyz-files\0"; find -L other/dir -name \*.xyz -print0 | sort -z; } | xargs -0 ./program --abc-files

関連情報