xargsで複数のコマンドを実行する


310
cat a.txt | xargs -I % echo %

上記の例では、xargsがecho %コマンド引数として使用されます。しかし、場合によっては、引数を処理するために、1つではなく複数のコマンドが必要になります。例えば:

cat a.txt | xargs -I % {command1; command2; ... }

しかし、xargsはこの形式を受け入れません。私が知っている1つの解決策は、コマンドをラップする関数を定義できることですが、それはパイプラインではないため、私はそれを好みません。別の解決策はありますか?


1
これらの答えのほとんどはセキュリティの脆弱性です潜在的に良い答えについては、こちらをご覧ください。
Mateen Ulhaq

2
私はほとんどすべてにxargsを使用していますが、文字列内にコマンドを入れてサブシェルを明示的に作成することは嫌いです。私はwhile複数のコマンドを含むことができるループにパイプする方法を学ぶ寸前です。
Sridhar Sarnobat

回答:


443
cat a.txt | xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

...または猫の無用な使用なし:

<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _

細かい点のいくつかを説明するには:

  • 使用"$arg"の代わりに、%(との不在-Ixargs、コマンドライン)は、セキュリティ上の理由からである:上のデータを渡すshのコマンドライン引数リストの代わりに、データのような(含まれている可能性があることをコード防止のコンテンツに代入$(rm -rf ~)、特にを取るために悪意のある例)コードとして実行されることから。

  • 同様に、の使用は、入力ファイルの各行を個別のデータ項目として扱う-d $'\n'原因となるGNU拡張xargsです。これまたは-0(改行の代わりにNULを期待する)は、xargsが読み取るストリームにシェルのような(ただし完全にシェル互換ではない)解析を適用しないようにするために必要です。(GNU xargsがない場合は、を使用tr '\n' '\0' <a.txt | xargs -0 ...せずに行指向の読み取りを行うことができます-d)。

  • _プレースホルダであるため$0、その結果によって追加された他のデータ値xargsとなる$1値の既定のセットであることを起こるれ、以降とforループが反復処理します。


58
慣れていない人のためにsh -c、各コマンドの後にセミコロンが、それはリストの最後のコマンドの場合でも、オプションではないことに注意してください- 。
Noah Sussman

6
少なくとも私の構成では、最初の「{」の直後にスペースが必要です。終了中括弧の前にスペースは必要ありませんが、Sussman氏が指摘したように、セミコロンを閉じる必要があります。
willdye 2012年

4
この答えは、以前の周りの中括弧を持っていたcommand1command2。後で必要ないことに気づきました。
キーストンプソン

24
セミコロンに関する上記のコメントを明確にするために、セミコロンを閉じる前に必要とされる}sh -c '{ command1; command2; }' -- but it's not required at the end of a command sequence that doesn't use braces: SH -c「Command1を。command2'`
キース・トンプソン

8
あなたはしているが含む場合%に渡されたあなたの文字列の文字のどこかにsh -c、これはセキュリティ上の脆弱性の傾向がある:含むファイル名$(rm -rf ~)'$(rm -rf ~)'(!と共通UNIXファイルシステム上のファイル名の中に持っている完全に合法ストリングです)誰かの原因になります非常に悪い日。
チャールズダフィー

35

GNU Parallelを使用すると、次のことができます。

cat a.txt | parallel 'command1 {}; command2 {}; ...; '

詳細については、紹介動画をご覧くださいhttps : //www.youtube.com/playlist?list=PL284C9FF2488BC6D1

セキュリティ上の理由から、パッケージマネージャーを使用してインストールすることをお勧めします。しかし、それができない場合は、この10秒のインストールを使用できます。

10秒のインストールでは、フルインストールが試行されます。それが失敗した場合、個人的なインストール。それが失敗した場合、最小限のインストール。

$ (wget -O - pi.dk/3 || lynx -source pi.dk/3 || curl pi.dk/3/ || \
   fetch -o - http://pi.dk/3 ) > install.sh
$ sha1sum install.sh | grep 3374ec53bacb199b245af2dda86df6c9
12345678 3374ec53 bacb199b 245af2dd a86df6c9
$ md5sum install.sh | grep 029a9ac06e8b5bc6052eac57b2c3c9ca
029a9ac0 6e8b5bc6 052eac57 b2c3c9ca
$ sha512sum install.sh | grep f517006d9897747bed8a4694b1acba1b
40f53af6 9e20dae5 713ba06c f517006d 9897747b ed8a4694 b1acba1b 1464beb4
60055629 3f2356f3 3e9c4e3c 76e3f3af a9db4b32 bd33322b 975696fc e6b23cfb
$ bash install.sh

56
未知のサイトからランダムスクリプトを実行してツールをインストールすることは恐ろしい習慣です。Parallelには、一般的なディストリビューション向けの一般的なパッケージがあり、ランダムなwget | sh ...よりも(ある程度)信頼できます
mdrozdziel

4
最も簡単な攻撃ベクトルは何かを見てみましょう:Pi.dkはGNU Parallelの作者によって制御されているため、サーバーに侵入したりDNSを乗っ取ったりしなければならないことを攻撃します。多くの場合、ディストリビューションの公式パッケージを引き継ぐために、パッケージのメンテナンスを自発的に行うことができます。したがって、あなたは一般的に正しいかもしれませんが、この特定のケースではあなたのコメントは正当化されないようです。
Ole Tange

10
実際には、pi.dkが作成者のものであることを知りません。実際にこれが事実であることを確認し、wgetでsslを使用する方法を考え、このコマンドが想定どおりの動作をすることを確認するのは少し作業です。公式パッケージに悪意のあるコードが含まれる可能性があるというあなたの主張は真実ですが、それはwgetパッケージにも当てはまります。
Fabian

3
OPが実行したいコマンドのそれぞれが順次的である必要がある場合、これは最善の解決策ではないかもしれませんね?
IcarianComplex 2015

2
@IcarianComplex -j1を追加すると修正されます。
Ole

26

これは、xargsもcatも使用しない別のアプローチです。

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt

2
与えられたように、バギー。をクリアしない限りIFS、ファイル名の先頭と末尾の空白は無視されます。追加しない限り-r、リテラルのバックスラッシュを含むファイル名では、それらの文字は無視されます。
Charles Duffy 2017年

質問には答えません。それは特にについて尋ねましたxargs。(これは、GNU xargs' -P<n>オプションと同様のことを行うために拡張するのは難しい)
Gert van den Berg

1
これは完璧に機能します。次のようなパイプコマンドとしても使用できます$ command | while read line; do c1 $line; c2 $line; done
Alexar

25

使用できます

cat file.txt | xargs -i  sh -c 'command {} | command2 {} && command3 {}'

{} =テキストファイルの各行の変数


8
これは安全ではありません。サブストリングとしてのfile.txtデータムが含まれている場合はどうなります$(rm -rf ~)か?
Charles Duffy

19

私が行うことの1つは、この関数を.bashrc / .profileに追加することです。

function each() {
    while read line; do
        for f in "$@"; do
            $f $line
        done
    done
}

その後、あなたは次のようなことができます

... | each command1 command2 "command3 has spaces"

これは、xargsまたは-execよりも詳細ではありません。また、その動作が必要な場合は、コマンドの任意の場所にある読み取りから値を挿入するように関数を変更することもできます。


1
過小評価されている答え、これは非常に便利です
charlesreid1

15

私はドライランモードを許可するスタイルを好みます(なし| sh):

cat a.txt | xargs -I % echo "command1; command2; ... " | sh

パイプでも動作します:

cat a.txt | xargs -I % echo "echo % | cat " | sh

2
これは、GNU xargsの-Pオプションを使用するまで機能します...(使用-execしない場合は、ほとんどの場合に使用します。find私の入力はほとんどがファイル名であるためです)
Gert van den Berg

13

パーティーに少し遅れました。

移行前に数千の小さなファイルでディレクトリを圧縮するには、以下の形式を使用します。コマンド内で単一引用符が必要ない場合は、機能するはずです。

いくつかの変更を加えると、それは誰かにとって役立つと確信しています。Cygwin(babun)でテスト済み

find . -maxdepth 1 ! -path . -type d -print0 | xargs -0 -I @@ bash -c '{ tar caf "@@.tar.lzop" "@@" && echo Completed compressing directory "@@" ; }'

find .ここを検索子
-maxdepth 1ディレクトリに移動しないでください
! -path .Exclude。/現在のディレクトリパス
-type dはディレクトリのみと一致
-print0します出力はnullバイトで区切られます\ 0
| xargsxargsへのパイプ
-0入力はnullで区切られたバイトです
-I @@プレースホルダーは@@です。@@を入力に置き換えます。
bash -c '...'Bashコマンドの実行
{...}コマンドのグループ化
&&前のコマンドが正常に終了した場合にのみ次のコマンドを実行(出口0)

final ;は重要です。それ以外の場合は失敗します。

出力:

Completed compressing directory ./Directory1 with meta characters in it
Completed compressing directory ./Directory2 with meta characters in it
Completed compressing directory ./Directory3 with meta characters in it

2018年7月の更新:

ハックが好きで遊んでいるなら、ここに興味深いものがあります:

echo "a b c" > a.txt
echo "123" >> a.txt
echo "###this is a comment" >> a.txt
cat a.txt
myCommandWithDifferentQuotes=$(cat <<'EOF'                                     
echo "command 1: $@"; echo 'will you do the fandango?'; echo "command 2: $@"; echo
EOF
)
< a.txt xargs -I @@ bash -c "$myCommandWithDifferentQuotes" -- @@

出力:

command 1: a b c
will you do the fandango?
command 2: a b c

command 1: 123
will you do the fandango?
command 2: 123

command 1: ###this is a comment
will you do the fandango?
command 2: ###this is a comment

説明:
-単一ライナースクリプトを作成し、変数に格納
- xargs読み込むa.txtようにして実行bashするスクリプト
- @@行全体が渡されたことを確認するたびになります
-パッティング@@後に--なりますが、確か@@に、位置パラメータ入力としているbashコマンドではなく、bashスタートOPTION、すなわち-cそれ自体が意味するようにrun command

--魔法です、それは他の多くのもので、すなわちsshkubectl


1
私はこのタイプのものでセットアップを使用しました:(find . -type f -print0|xargs -r0 -n1 -P20 bash -c 'f="{}";ls -l "$f"; gzip -9 "$f"; ls -l "$f.gz"'ループを変換するときに少し簡単です)
Gert van den Berg

1
私の以前のコメントの後半の編集:(ファイル名に二重引用符が含まれている場合、セキュリティの問題があります...)(それ"$@"を回避する唯一の方法は使用することです...(-n1パラメーターの数を制限する場合))
ゲルトファンデンバーグ

--シェルがこれ以上のオプションを受け入れないことを示すために使用することに注意してください。これにより---あまりにも後が存在することができます。たとえばgrep -r、パターンに含めると、そうしないと、非常に興味深く混乱する出力が得られます-。あなたがそれを言い表す方法はこれを明確にしませんが、これがどのように機能するかを実際に説明しません。IircそれはPOSIXのものですが、とにかくこれを指摘する価値があると思います。考慮すべきことだけです。そして、私はそのボーナスが大好きです!
Pryftan

10

これが最も安全なバージョンのようです。

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

-0除去することができ、trリダイレクト(またはファイルと置き換えがNULLに置き換えることができる)の代わりにファイルを分離した。私が主に使用するので、それはそこに主にあるxargsfindして-print0出力)(これも上の関連するかもしれないxargs無しバージョン-0拡張)

argsは実行時にパラメータを配列としてシェルに渡すため、安全です。シェル(少なくともbash)は、すべてを使用して取得したときに、変更されていない配列として他のプロセスに渡します["$@"][1]

を使用...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' ''する場合、文字列に二重引用符が含まれていると割り当てが失敗します。これは、-iまたはを使用するすべてのバリアントに当てはまります-I。(文字列に置き換えられるため、予期しない文字(引用符、バッククォート、ドル記号など)を入力データに挿入することで、いつでもコマンドを挿入できます)

コマンドが一度に1つのパラメーターしか取らない場合:

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n1 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

またはやや少ないプロセスで:

tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'for f in "$@"; do command1 "$f"; command2 "$f"; done;' ''

GNU xargsまたはその他の-P拡張機能があり、32のプロセスを並行して実行する場合、各プロセスのパラメーターはそれぞれ10以下です。

tr '[\n]' '[\0]' < a.txt | xargs -r0 -n10 -P32 /bin/bash -c 'command1 "$@"; command2 "$@";' ''

これは、入力の特殊文字に対して堅牢でなければなりません。(入力がnullで区切られている場合。)tr改行が含まれている行がある場合、バージョンは無効な入力を取得しますが、改行で区切られたファイルではそれが避けられません。

の空白の最初のパラメーターbash -cはこれが原因です:(bashmanページから)(ありがとう@clacke)

-c   If the -c option is present, then  commands  are  read  from  the  first  non-option  argument  com
     mand_string.   If there are arguments after the command_string, the first argument is assigned to $0
     and any remaining arguments are assigned to the positional parameters.  The assignment  to  $0  sets
     the name of the shell, which is used in warning and error messages.

これは、ファイル名に二重引用符があっても機能するはずです。適切にサポートするシェルが必要です"$@"
Gert van den Berg

bashのargv [0]引数がありません。bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke

3
これは、xargsが行うことについてではありません。bashwith -cは、最初に(コマンドの後に)プロセスの名前となる1つの引数を受け取り、次に位置引数を受け取ります。bash -c 'echo "$@" ' 1 2 3 4何が出るか試してみてください。
clacke

Bobby-Tabledを取得しない安全なバージョンがあると便利です。
Mateen Ulhaq

8

私にとってうまくいく別の可能な解決策は次のようなものです-

cat a.txt | xargs bash -c 'command1 $@; command2 $@' bash

最後の「bash」に注意してください-私はそれがargv [0]としてbashに渡されると想定しています。この構文にそれがないと、各コマンドの最初のパラメーターは失われます。どんな言葉でもいいです。

例:

cat a.txt | xargs -n 5 bash -c 'echo -n `date +%Y%m%d-%H%M%S:` ; echo " data: " $@; echo "data again: " $@' bash

5
引用符を付けない場合は"$@"、引数リストを文字列分割してグロブ展開しています。
Charles Duffy

2

このための私の現在のBKMは

... | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'

これがperlを使用していることは残念ですが、これはbashよりもインストールされる可能性が低くなります。しかし、受け入れられた回答よりも多くの入力を処理します。(perlに依存しないユビキタスバージョンを歓迎します。)

@KeithThompsonの提案

 ... | xargs -I % sh -c 'command1; command2; ...'

素晴らしい-入力にシェルコメント文字#がない場合、最初のコマンドの一部と2番目のコマンドのすべてが切り捨てられます。

入力がlsやfindなどのファイルシステムリストから派生し、エディターが名前に#を含む一時ファイルを作成する場合、ハッシュ#は非常に一般的です。

問題の例:

$ bash 1366 $>  /bin/ls | cat
#Makefile#
#README#
Makefile
README

おっと、ここに問題があります:

$ bash 1367 $>  ls | xargs -n1 -I % sh -i -c 'echo 1 %; echo 2 %'
1
1
1
1 Makefile
2 Makefile
1 README
2 README

ああ、それは良いです:

$ bash 1368 $>  ls | xargs -n1 -I % perl -e 'system("echo 1 %"); system("echo 2 %");'
1 #Makefile#
2 #Makefile#
1 #README#
2 #README#
1 Makefile
2 Makefile
1 README
2 README
$ bash 1369 $>  

5
:#問題はイースリー引用符を使用して解決することができるls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
GPL
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.