使用 xargs 运行多个命令
Posted
技术标签:
【中文标题】使用 xargs 运行多个命令【英文标题】:Running multiple commands with xargs 【发布时间】:2011-10-20 23:35:22 【问题描述】:cat a.txt | xargs -I % echo %
在上面的示例中,xargs 将echo %
作为命令参数。但在某些情况下,我需要多个命令来处理参数而不是一个。例如:
cat a.txt | xargs -I % command1; command2; ...
但是 xargs 不接受这种形式。我知道的一个解决方案是我可以定义一个函数来包装命令,但我想避免这种情况,因为它很复杂。有没有更好的解决方案?
【问题讨论】:
这些答案大多是安全漏洞。 See here for a potentially good answer. 我几乎对所有事情都使用 xargs,但我讨厌将命令放在字符串中并显式创建子 shell。我即将学习如何通过管道进入可以包含多个命令的while
循环。
在以下输入上测试解决方案:"
、*
、a two spaces b
、$(echo Do not print this)
。如果这些不能按预期工作,则解决方案中可能还有其他错误。
【参考方案1】:
cat a.txt | xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _
...或者,没有Useless Use Of cat:
<a.txt xargs -d $'\n' sh -c 'for arg do command1 "$arg"; command2 "$arg"; ...; done' _
解释一些细节:
出于安全原因,使用 "$arg"
代替 %
(并且在 xargs
命令行中缺少 -I
)是出于安全原因:在 sh
的命令行参数上传递数据list 而不是将其替换为代码可防止数据可能包含的内容(例如$(rm -rf ~)
,举一个特别恶意的例子)作为代码执行。
同样,-d $'\n'
的使用是一个 GNU 扩展,它导致 xargs
将输入文件的每一行视为一个单独的数据项。这或-0
(它需要 NUL 而不是换行符)对于防止 xargs 尝试对其读取的流应用类似 shell(但 不太 shell 兼容)的解析是必要的。 (如果你没有 GNU xargs,你可以使用tr '\n' '\0' <a.txt | xargs -0 ...
来获得面向行的阅读,而不需要-d
)。
【讨论】:
对于那些不熟悉sh -c
的人——请注意,每个命令后的分号不是可选的,即使它是列表中的最后一个命令。
至少在我的配置中,开头的“”后面必须有一个空格。结束大括号之前不需要空格,但正如 Sussman 先生所说,您确实需要一个结束分号。
这个答案之前在 command1
和 command2
周围有花括号;后来我意识到它们没有必要。
为了澄清上面关于分号的cmets,在结束
之前需要一个分号:sh -c ' command1; command2; ' -- but it's not required at the end of a command sequence that doesn't use braces:
sh -c 'command1;命令2'`
如果您在传递给sh -c
的字符串中的某处包含%
字符,那么这很容易出现安全漏洞:包含$(rm -rf ~)'$(rm -rf ~)'
的文件名(这是一个完全合法的子字符串在常见 UNIX 文件系统上的文件名中包含!)将导致某人非常糟糕的一天。【参考方案2】:
这只是另一种没有 xargs 和 cat 的方法:
while read stuff; do
command1 "$stuff"
command2 "$stuff"
...
done < a.txt
【讨论】:
Buggy,如给定的。除非您清除IFS
,否则它将忽略文件名中的前导和尾随空格;除非您添加 -r
,否则带有文字反斜杠的文件名将忽略这些字符。
不回答问题。它专门询问了xargs
。 (这很难扩展为类似于 GNU xargs
'-P<n>
选项)
这很好用。您也可以将其用作管道命令,例如 $ command | while read line; do c1 $line; c2 $line; done
【参考方案3】:
我要做的一件事是在 .bashrc/.profile 中添加这个函数:
function each()
while read line; do
for f in "$@"; do
$f $line
done
done
然后你可以做类似的事情
... | each command1 command2 "command3 has spaces"
比 xargs 或 -exec 更简洁。如果您还需要该行为,您还可以修改函数以将读取的值插入到每个命令中的任意位置。
【讨论】:
被低估的答案,这非常方便 如果输入连续有两个空格或*
,则无法正常工作。【参考方案4】:
使用 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 883c667e01eed62f975ad28b6d50e22a
12345678 883c667e 01eed62f 975ad28b 6d50e22a
$ md5sum install.sh | grep cc21b4c943fd03e93ae1ae49e28573c0
cc21b4c9 43fd03e9 3ae1ae49 e28573c0
$ sha512sum install.sh | grep da012ec113b49a54e705f86d51e784ebced224fdf
79945d9d 250b42a4 2067bb00 99da012e c113b49a 54e705f8 6d51e784 ebced224
fdff3f52 ca588d64 e75f6033 61bd543f d631f592 2f87ceb2 ab034149 6df84a35
$ bash install.sh
【讨论】:
通过运行来自未知站点的随机脚本来安装工具是一种可怕的做法。 Parallel 为流行发行版提供官方软件包,比随机 wget|sh 更值得信任(在某种程度上)... 让我们看看什么是最简单的攻击向量:Pi.dk 由 GNU Parallel 的作者控制,因此要进行攻击,您必须侵入服务器或接管 DNS。要接管发行版的官方软件包,您通常可以自愿维护该软件包。因此,尽管您总体上可能是对的,但在这种特殊情况下,您的评论似乎是不合理的。 在实践中我不知道 pi.dk 属于作者。实际上验证是这种情况,考虑如何在 wget 中使用 ssl 并检查该命令是否完成了它应该做的事情是一些工作。您认为官方包可能包含恶意代码的观点是正确的,但这也适用于 wget 包。 如果 OP 要执行的每个命令都必须是顺序的,这可能不是最佳解决方案,对吗? @IcarianComplex 添加 -j1 将解决该问题。【参考方案5】:对我有用的另一种可能的解决方案是 -
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
【讨论】:
如果你不引用"$@"
,那么你就是在对参数列表进行字符串分割和全局扩展。【参考方案6】:
你可以使用
cat file.txt | xargs -i sh -c 'command | command2 && command3 '
= 文本文件中每一行的变量
【讨论】:
这是不安全的。如果您的file.txt
包含一个以$(rm -rf ~)
作为子字符串的数据怎么办?
这对我来说效果很好,幸运的是 zoneinfo 时区定义都不包含 rm -rf ;)
+1。令人难以置信的是人们会在不需要的地方(例如处理 IP 地址、PID 或 USB 设备名称的列表)的安全性上花费多少精力
然而,作为“通用解决方案”,安全问题应该被(并且曾经)正确地注意到。除非您了解风险,否则不应将命令用于不受信任或未经处理的输入。如果您确实相信自己的意见,那就接受吧。【参考方案7】:
我目前的 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 $>
【讨论】:
# 使用引号可以轻松解决问题:ls | xargs -I % sh -c 'echo 1 "%"; echo 2 "%"'
【参考方案8】:
我更喜欢允许空运行模式的样式(没有| sh
):
cat a.txt | xargs -I % echo "command1; command2; ... " | sh
也适用于管道:
cat a.txt | xargs -I % echo "echo % | cat " | sh
【讨论】:
这有效,直到您想使用 GNU xargs 的-P
选项...(如果不是,我主要在 find
上使用 -exec
,因为我的输入主要是文件名)跨度>
输入失败:"【参考方案9】:
这似乎是最安全的版本。
tr '[\n]' '[\0]' < a.txt | xargs -r0 /bin/bash -c 'command1 "$@"; command2 "$@";' ''
(-0
可以删除,tr
替换为重定向(或者文件可以替换为空分隔文件)。它主要在那里,因为我主要使用 xargs
和 find
带有-print0
输出)(这也可能与没有-0
扩展的xargs
版本相关)
这是安全的,因为 args 在执行时会将参数作为数组传递给 shell。当使用["$@"][1]
获得所有数据时,shell(至少bash
)会将它们作为未更改的数组传递给其他进程
如果您使用...| 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 "$@";' ''
这应该对输入中的任何特殊字符都具有鲁棒性。 (如果输入为空分隔符。)tr
版本如果某些行包含换行符,则会得到一些无效输入,但对于换行符分隔的文件,这是不可避免的。
bash -c
的空白第一个参数是由于:(来自bash
man page)(感谢@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.
【讨论】:
即使在文件名中使用双引号也应该有效。这需要一个能够正确支持"$@"
的外壳
您缺少 bash 的 argv[0] 参数。 bash -c 'command1 "$@"; command2 "$@";' arbitrarytextgoeshere
这与 xargs 的作用无关。 bash
和 -c
首先(在命令之后)一个参数,该参数将是进程的名称,然后是位置参数。试试bash -c 'echo "$@" ' 1 2 3 4
看看会发生什么。
很高兴有一个没有 Bobby-Tabled 的安全版本。【参考方案10】:
试试这个:
git config --global alias.all '!f() find . -d -name ".git" | sed s/\\/\.git//g | xargs -P10 -I git --git-dir=/.git --work-tree= $1; ; f'
它并行运行 10 个线程,并执行您想要的任何 git 命令对文件夹结构中的所有 repos。不管repo是一层还是n层。
例如:git all pull
【讨论】:
您的示例非常有用,但它足够复杂,解释会有所帮助。它似乎没有回答有关如何使用 xargs 执行多个命令的问题。您的示例所做的是git --git-dir=A1/.git --work-tree=A1 pull
在您的示例中,其中 A1 是它找到的存储库之一。问题是如何执行ls -al ; rm -f
之类的操作(即,给 xargs 的每一行两个命令不是一个)。
我的评论似乎不合时宜。它根本没有回答最初的问题。我确定我在另一个问题上发布了这个:D 我的评论所做的是准备一个 git 别名。该别名允许在位于子目录中的所有 repos 上运行 git 命令。 @SteventheEasilyAmused,你认为我应该删除评论吗?【参考方案11】:
我有一个好主意来解决这个问题。
只写一个commanmcmd
就可以了
find . -type f | xargs -i mcmd echo @@ cat @pipe sed -n '1,3p'
mcmd
内容如下:
echo $* | sed -e 's/@@/\n/g' -e 's/@pipe/|/g' | csh
【讨论】:
我为你不满足于sh -c
解决方案而鼓掌,这不适合我。以上是关于使用 xargs 运行多个命令的主要内容,如果未能解决你的问题,请参考以下文章
Linux xargs 命令(给命令传递参数的一个过滤器,也是组合多个命令的一个工具)(通常与管道配合使用)