关于bash:使用xargs调用多个命令

Calling multiple commands with xargs

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

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

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

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


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

...或者没有对猫的无用使用:

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

要解释一些更好的观点:

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

  • 类似地,-d $'
    '
    的使用是GNU扩展,它导致xargs将输入文件的每一行都视为一个单独的数据项。要防止xargs尝试对读取的流进行类似于shell的解析(但不完全与shell兼容),必须使用this或-0(期望使用NUL而不是换行符)。 (如果没有GNU xargs,则可以使用tr '
    ' '\0' to get line-oriented reading without -d)。

  • _$0的占位符,因此xargs添加的其他数据值将变为$1并向前,这恰好是for循环迭代的默认值集。


使用GNU Parallel,您可以:

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

观看介绍性视频以了解更多信息:https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1


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

1
2
3
4
5
while read stuff; do
  command1"$stuff"
  command2"$stuff"
  ...
done < a.txt


您可以使用

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

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


我要做的一件事是将.bashrc / .profile添加到此函数:

1
2
3
4
5
6
7
function each() {
    while read line; do
        for f in"$@"; do
            $f $line
        done
    done
}

然后你可以做

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

它不如xargs或-exec冗长。您也可以修改该函数,以将读取值插入命令中的任意位置(如果需要),从而将其插入每个命令。


我更喜欢允许空运行模式(没有| sh)的样式:

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

也适用于管道:

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


晚会晚了。

我使用以下格式在迁移之前用数千个小文件压缩目录。如果您在命令中不需要单引号,则应该可以使用。

经过一些修改,我相信它将对某人有用。在Cygwin中测试(babun)

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

find .在这里找到
-maxdepth 1不要进入子目录
! -path .排除。 /当前目录路径
-type d仅匹配目录
-print0用空字节分开输出 0
| xargs管道到xargs
-0输入为空分隔的字节
-I @@占位符为@@。用输入替换@@。
bash -c '...'运行Bash命令
{...}命令分组
&&仅在上一个命令成功退出(退出0)时执行下一个命令

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

输出:

1
2
3
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月更新:

如果您喜欢黑客和游戏,这里有一些有趣的事情:

1
2
3
4
5
6
7
8
9
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" -- @@

输出:

1
2
3
4
5
6
7
8
9
10
11
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

--是神奇的,它可以与许多其他东西一起使用,即ssh甚至kubectl


对我有用的另一种可能的解决方案是-

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

注意最后的" bash"-我假设它以argv [0]的形式传递给bash。如果没有使用此语法,则会丢失每个命令的第一个参数。可能是任何单词。

例:

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


这似乎是最安全的版本。

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

(可以删除-0并将tr替换为重定向(或者可以将文件替换为空分隔的文件)。它主要在此,因为我主要将xargsfind输出)(这可能与不带-0扩展名的xargs版本有关)

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

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

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

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

或使用较少的流程:

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

如果您具有GNU xargs或另一个带有-P扩展名的文件,并且希望并行运行32个进程,则每个命令的每个参数不得超过10个:

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

对于输入中的任何特殊字符,这应该是可靠的。 (如果输入为null分隔。)如果某些行包含换行符,则tr版本将获得一些无效的输入,但是对于换行符分隔的文件,这是不可避免的。

bash -c的空白第一个参数是由于以下原因:(来自bash手册页)(感谢@clacke)

1
2
3
4
-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.


我目前的BKM是

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

不幸的是,它使用了perl,它比bash不太可能被安装。但它处理的输入要多于可接受的答案。 (我欢迎不依赖perl的无处不在的版本。)

@KeithThompson的建议

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

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

如果输入源于文件系统列表(例如ls或find),并且您的编辑器创建名称为#的临时文件,则哈希号#很常见。

问题示例:

1
2
3
4
5
$ bash 1366 $>  /bin/ls | cat
#Makefile#
#README#
Makefile
README

糟糕,这是问题所在:

1
2
3
4
5
6
7
8
$ bash 1367 $>  ls | xargs -n1 -I % sh -i -c 'echo 1 %; echo 2 %'
1
1
1
1 Makefile
2 Makefile
1 README
2 README

嗯,这样更好:

1
2
3
4
5
6
7
8
9
10
$ 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 $>