web-dev-qa-db-ja.com

パイプの2番目の側にXargs?

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

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

File1の各行が3番目のパイプの最後にあるgrepの値になると期待しています。期待どおりに機能していません。

これは-I{}パイプにぶつかると、交換するものを探すのをやめますか?これを回避する方法はありますか?

5

これは、パイプを作成したり、リダイレクトを実行したりするためにシェルが必要なためです。 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のコード引数で展開されるため、シェルコードとして解釈されます。たとえば、_file1.txt_の行の1つが$(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
_

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

_grep -f file1.txt file2.txt
_

ただし、その場合、それは_file1.txt_の各行に1つの正規表現であり、xargsによって実行される特別な引用処理はありません。

xargsは、デフォルトで、入力を空白のリスト(一部の実装ではスペースとタブのみ、その他の実装では現在のロケールの_[:blank:]_文字クラス内のいずれか)またはバックスラッシュと単一引用符と二重引用符を使用して、区切り文字(改行はバックスラッシュでしかエスケープできません)または相互にエスケープできます。

たとえば、次のような入力では:

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

_-I{}_なしのxargsは、_a "b"_、_bar baz_および_x<newline>y_をコマンドに渡します。

_-I{}_を使用すると、xargsは1行につき1ワードを取得しますが、追加の処理をいくつか行います。先頭の(ただし末尾ではない)空白は無視されます。空白は区切り文字とは見なされなくなりましたが、見積もり処理は引き続き行われています。

上記の入力では、_xargs -I{}_は1つの_a "b" foo bar x<newline>y_引数をコマンドに渡します。また、POSIXで要求される1つの多くのシステムは、単語が255文字を超える場合は機能しません。全体として、_xargs -I{}_はかなり役に立たない。

各行をコマンドの引数として逐語的に渡す場合は、GNU xargs _-d '\n'_拡張子を使用できます。

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

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

_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
_
13

何をしようとしているかに応じて、xargsを完全にスキップして、代わりにこのソリューションを使用する方がよい場合があります。

grep -f file1.txt file2.txt

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

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

-fフラグは POSIXで指定 であるため、かなり移植可能です。

8
Kevin