使用 xargs 运行多个命令

cat a.txt | xargs -I % echo %

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

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

xargs不接受这种形式。我知道的一个解决方案是,我可以定义一个函数来包装命令,但是我想避免这样做,因为它很复杂。还有更好的解决办法吗?

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

...或者,不带无用地使用cat:

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

来解释一些细节:

  • 使用"$arg"而不是%(并且在xargs命令行中没有-I)是出于安全原因:在sh的命令行参数列表中传递数据而不是将其替换为代码,可以防止数据可能包含的内容(例如$(rm -rf ~),这是一个特别恶意的例子)作为代码执行。

  • 类似地,-d $'\n'的使用是一个GNU扩展,它导致xargs将输入文件的每一行视为一个单独的数据项。这或-0(期望NULs而不是换行符)都是必要的,以防止xargs试图对其读取的流应用类似shell(但不完全兼容shell)的解析。(如果你没有GNU xargs,你可以使用tr '\n' '\0' <a.txt | xargs -0 ...来获得行读取,而不需要-d)。

  • _$0的占位符,这样由xargs添加的其他数据值就变成了$1,以此类推,这恰好是for循环迭代的默认值集。

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

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

我做的一件事是添加到.bashrc/。配置此功能:

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

然后你就可以做

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

它比xargs或-exec更简洁。如果还需要这种行为,还可以修改该函数,将读取的值插入到每个命令中的任意位置。

使用GNU Parallel,你可以做到:

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

出于安全原因,建议您使用包管理器 安装。但如果你做不到,你可以用这10秒 安装。< / p >

10秒安装将尝试完全安装;如果 失败了,个人安装;如果那失败了,一个最小值 安装。< / p >

$ (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

对我来说另一个可行的解决方案是——

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

你可以使用

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

{} =变量为文本文件中的每一行

我目前的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 $>

我更喜欢允许演练模式(没有| sh)的样式:

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

管道也适用:

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

这似乎是最安全的版本。

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手册页)(感谢@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.

试试这个:

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命令。无论回购是1层还是n层。

例句:git all pull

我有一个解决这个问题的好办法。 只写一个命令mcmd,然后你可以做

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