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

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

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

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

4

11 回答 11

511
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"而不是%(以及在命令行中不存在-I) :在的命令行参数列表中传递数据而不是将其替换到代码中可以防止数据可能包含的内容(例如,采取特别恶意示例)作为代码执行。xargssh$(rm -rf ~)

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

  • 是的_占位符$0,这样其他数据值由xargs成为$1和向前添加,这恰好是for循环迭代的默认值集。

于 2011-08-05T15:41:33.477 回答
48

您可以使用

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

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

于 2014-02-14T05:20:17.520 回答
43

使用 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
于 2012-10-05T14:40:17.537 回答
27

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

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

也适用于管道:

cat a.txt | xargs -I % echo "echo % | cat " | sh
于 2017-09-08T07:35:59.003 回答
26

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

while read stuff; do
  command1 "$stuff"
  command2 "$stuff"
  ...
done < a.txt
于 2011-08-05T15:51:14.797 回答
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 更简洁。如果您还需要该行为,您还可以修改函数以将读取的值插入到每个命令中的任意位置。

于 2012-09-28T01:24:22.503 回答
19

这似乎是最安全的版本。

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

-0可以删除并tr用重定向替换(或者文件可以用空分隔的文件替换)。它主要在那里,因为我主要使用xargswith输出)(这也可能与没有扩展名的find版本有关)-print0xargs-0

这是安全的,因为 args 在执行时会将参数作为数组传递给 shell。当bash所有都使用["$@"][1]

如果使用...| xargs -r0 -I{} bash -c 'f="{}"; command "$f";' '',如果字符串包含双引号,则赋值将失败。对于使用-ior的每个变体都是如此-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;' ''

如果你有 GNUxargs或其他-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.
于 2018-07-12T12:03:04.603 回答
10

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

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
于 2014-02-12T07:54:56.490 回答
3

我目前的 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 $>  
于 2016-01-24T16:47:26.500 回答
2

尝试这个:

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'

它并行运行十个线程,并对文件夹结构中的所有 repos 执行任何 git 命令。无论 repo 是一层还是 n 层。

例如:git all pull

于 2020-09-14T10:33:59.710 回答
0

我有解决问题的好主意。只写一个命令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
于 2021-04-30T03:30:58.787 回答