使用 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' &lt;a.txt | xargs -0 ... 来获得面向行的阅读,而不需要-d)。

1234563结束了。

【讨论】:

对于那些不熟悉sh -c的人——请注意,每个命令后的分号不是可选的,即使它是列表中的最后一个命令。 至少在我的配置中,开头的“”后面必须有一个空格。结束大括号之前不需要空格,但正如 Sussman 先生所说,您确实需要一个结束分号。 这个答案之前在 command1command2 周围有花括号;后来我意识到它们没有必要。 为了澄清上面关于分号的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&lt;n&gt; 选项) 这很好用。您也可以将其用作管道命令,例如 $ 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 替换为重定向(或者文件可以替换为空分隔文件)。它主要在那里,因为我主要使用 xargsfind带有-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的使用

Linux xargs 命令(给命令传递参数的一个过滤器,也是组合多个命令的一个工具)(通常与管道配合使用)

Linux xargs 命令(给命令传递参数的一个过滤器,也是组合多个命令的一个工具)(通常与管道配合使用)

linux shell脚本学习xargs命令使用详解

linux中xargs命令的使用方式

linux下xargs命令用法详解