使用xargs运行多个命令


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

在上面的示例中,xargs echo %作为命令参数。但是在某些情况下,我需要多个命令而不是一个来处理参数。例如:

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

但是xargs不接受这种形式。我知道的一个解决方案是,我可以定义一个包装命令的函数,但这不是管道,我不喜欢它。还有其他解决方案吗?


1
这些答案大多数都是安全漏洞请参阅此处,以获得一个可能的好答案。
Mateen Ulhaq,

2
我几乎对所有东西都使用xargs,但我讨厌将命令放在字符串中并显式创建子外壳。我正处于学习如何将管道传递到while可以包含多个命令的循环的边缘。
Sridhar Sarnobat '19

Answers:


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为防止xargs尝试对读取的流进行类shell(但不完全与 shell兼容)的解析,必须使用this或(期望使用NUL而不是换行符)。(如果您没有GNU xargs,则可以不用tr '\n' '\0' <a.txt | xargs -0 ...来获取面向行的阅读-d)。

  • _是一个占位符$0,以使得加入由其它数据值xargs成为$1和向前,这恰好是默认设定值的一个for循环迭代。


58
对于那些不熟悉的人sh -c-请注意,每个命令后的分号不是可选的,即使它是列表中的最后一个命令。
诺亚·萨斯曼

6
至少在我的配置中,初始“ {”之后必须紧跟一个空格。结束的花括号前不需要空格,但是正如Sussman先生所指出的,您确实需要用分号隔开。
Willdye 2012年

4
这个答案以前在command1和周围有花括号command2;后来我意识到它们不是必需的。
基思·汤普森

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'`
Keith Thompson

8
如果你包括%在你的字符串传递给字符的地方sh -c,那么这是容易出现安全漏洞:包含文件名的$(rm -rf ~)'$(rm -rf ~)'(这是一个完全合法的子串有共同的UNIX文件系统的文件名之内!)会引起别人一个非常糟糕的一天。
Charles Duffy

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 2013年

10
实际上,我不知道pi.dk是作者的。实际验证情况是否正确,考虑如何在wget中使用ssl并检查此命令是否可以完成应做的工作。您认为官方软件包可以包含恶意代码的观点是正确的,但是wget软件包也是如此。
Fabian

3
如果OP要执行的每个命令必须顺序执行,这可能不是最佳解决方案,对吗?
IcarianComplex

2
@IcarianComplex添加-j1将解决此问题。
Ole Tange

26

这只是没有xargs或cat的另一种方法:

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

2
越野车,如给定。除非您清除IFS,否则它将忽略文件名中的前导和尾随空格;除非您添加-r,否则带文字反斜杠的文件名将忽略这些字符。
Charles Duffy

不回答问题。它专门询问了一下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

我要做的一件事是将.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选项为止(如果没有,我主要使用-execon 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用空字节分隔输出\ 0
| xargs到xargs的管道
-0输入是空分隔字节的
-I @@占位符是@@。用输入替换@@。
bash -c '...'运行Bash命令
{...}命令分组
&&仅在前一个命令成功退出(退出0)时才执行下一个命令

最终;很重要,否则它将失败。

输出:

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并执行-确保每次传递整行-放置在后确保将其作为位置参数输入到命令,而不是start ,即喜欢自己,这意味着bash
@@
@@--@@bashbashOPTION-crun command

--是神奇的,它与许多其他的东西,即ssh,即使kubectl


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如果要限制参数的数量))
Gert van den Berg

应该指出的--是,shell使用它来表示不再接受任何选项。这也允许在-后面有一个--。如果不添加,则可能会得到非常有趣且令人困惑的输出,例如grep -r当您包含在模式中时-!尽管您所说的话方式并不能澄清这一点,但并不能真正解释其工作原理。Iirc这是POSIX的事情,但我认为无论如何都值得指出。只是要考虑的事情。而且我喜欢奖金!
Pryftan

10

这似乎是最安全的版本。

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

-0可以删除并tr用重定向替换(或该文件可以用空分隔的文件代替)。它主要在此,因为我主要xargsfindwith -print0输出配合使用)(这也可能与xargs没有-0扩展名的版本有关)

这是安全的,因为args在执行时会将参数作为数组传递给Shell。bash当使用以下命令获取所有内容时,shell(至少)会将它们作为未更改的数组传递给其他进程["$@"][1]

如果使用...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '',则在字符串包含双引号的情况下分配将失败。对于使用-i或的每个变体来说都是如此-I。(由于将其替换为字符串,因此您始终可以通过在输入数据中插入意外字符(例如引号,反引号或美元符号)来注入命令)

如果这些命令一次只能使用一个参数:

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是由于以下原因:(来自bash手册页)(感谢@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 '18

您缺少bash的argv [0]参数。bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
clacke

3
这与xargs无关。bashwith -c首先(在命令之后)使用一个参数作为流程的名称,然后使用位置参数。尝试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; ...'

太好了-除非您在输入中输入了shell注释字符#,否则第一个命令的一部分和第二个命令的所有部分都会被截断。

如果输入源于文件系统列表(例如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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.