パイプの2番目の側にXargsを実行しますか?

パイプの2番目の側にXargsを実行しますか?

私は次のことをやろうとしています:

cat file1.txt | xargs -I{} "cat file2.txt | grep {}"

file1 の各行が 3 番目のパイプの末尾の grep の値になることを期待しています。期待どおりに動作しません。

-I{}これは、パイプに当たると交換するものを探すのをやめるからでしょうか? これを回避する方法はありますか?

答え1

これは、パイプを作成したりリダイレクトを実行したりするためにシェルが必要になるためです。 はcat連結するコマンドであり、1 つのファイルに対してのみ使用してもあまり意味がありません。

cat file1.txt | xargs -I{} sh -c 'cat file2.txt | grep -e "$1"' sh {}

するないする:

cat file1.txt | xargs -I{} sh -c 'cat file2.txt | grep -e {}'

これはコマンド インジェクションの脆弱性に相当します。 は{}コード引数で に展開され、shシェル コードとして解釈されます。 たとえば、 の行の 1 つが である場合、file1.txt$(reboot)を呼び出しますreboot

(-eまたは を使用することもできます--) も重要です。これがないと、 で始まる正規表現で問題が発生します-

代わりにリダイレクトを使用して上記を簡略化できますcat:

< file1.txt xargs -I{} sh -c '< file2.txt grep -e "$1"' sh {}

または、リダイレクトを使用する代わりに、ファイル名を引数として渡すだけで、grep次の部分を省略することもできますsh

< file1.txt xargs -I{} grep -e {} file2.txt

grep1 回の呼び出しですべての正規表現を一度に検索するように指示することもできます。

grep -f file1.txt file2.txt

ただし、その場合、 の各行に対して 1 つの正規表現だけになりfile1.txt、 による特別な引用符処理は行われないことに注意してくださいxargs

xargsデフォルトでは、入力を空白 (一部の実装ではスペースとタブのみ、その他の実装では[:blank:]現在のロケールの文字クラスの任意の文字) または改行で区切られた単語のリストと見なします。これらの単語に対して、バックスラッシュ、一重引用符、二重引用符を使用して区切り文字をエスケープできます (ただし、改行はバックスラッシュでのみエスケープできます)。

たとえば、次のような入力があったとします。

 'a "b'\" "bar baz" x\
y

xargsなしでは、コマンドに-I{}合格しません。a "b"bar bazx<newline>y

を使用すると-I{}xargs1 行につき 1 つの単語が取得されますが、追加の処理が行われます。先頭の空白は無視されますが、末尾の空白は無視されません。空白は区切り文字とは見なされなくなりますが、引用符処理は引き続き実行されます。

上記の入力では、コマンドにxargs -I{}1 つの引数が渡されますa "b" foo bar x<newline>y。また、POSIX で要求されているように、多くのシステムでは、単語の長さが 255 文字を超えると機能しないことに注意してください。全体として、これはxargs -I{}ほとんど役に立ちません。

各行をコマンドの引数としてそのまま渡したい場合は、GNUxargs -d '\n'拡張機能を使用できます。

< file1.txt xargs -d '\n' -n 1 grep file2.txt -e

(ここでは、引数の後にオプションを渡すことができる GNU の別の拡張機能に依存していますgrep(POSIX に正しいものが環境にない場合)、または移植可能です:

sed "s/'/'\\\\\\''/g;s/.*/'&'/" file1.txt | xargs -n1 sh -c '
  for line do
    grep -e "$line" file2.txt
  done' sh

それぞれをご希望の場合は言葉(引用符はまだ認識されます)それぞれfile1.txtに対してラインを検索するには (1 行に 1 つの単語がある場合、末尾のスペースの問題も回避できます)、xargs -n1の代わりに を単独で使用できます-I

< file1.txt xargs -n1 sh -c '
  for word do
    grep -e "$word" file2.txt
  done' sh

先頭と末尾の空白を削除するには (ただし引用符処理はxargs行いません)、次のように実行することもできます。

unset IFS # restore word splitting to its default
while read -r regexp; do
  grep -e "$regexp" file2.txt
done < file1.txt

答え2

xargs何をしようとしているかに応じて、完全にスキップして、代わりに次の解決策を採用する方がよい場合があります。

grep -f file1.txt file2.txt

これは元のコマンドとは異なります(Stéphane Chazelas の回答のように修正すると) 次のようになります。

  • 行は、どのパターンに一致するかに関係なく、出現順に印刷されますfile2.txt。コマンドでは、最初のパターンに一致するすべての行が印刷され、次に 2 番目のパターンに一致するすべての行が印刷され、以下同様に続きます。
  • 複数のパターンに一致する行は、正確に 1 回印刷されます。コマンドでは、一致するパターンごとに 1 回印刷されます。
  • -vとの両方を含むいくつかのフラグをより簡単に使用できます-c

-fPOSIXで規定そのため、持ち運びも適度に簡単です。

関連情報