421

如何让 xargs 对给定的每一行输入只执行一次命令?它的默认行为是将行分块并执行一次命令,将多行传递给每个实例。

来自http://en.wikipedia.org/wiki/Xargs

查找 /path -type f -print0 | xargs -0 rm

在此示例中,find 为 xargs 的输入提供一长串文件名。然后 xargs 将此列表拆分为子列表,并为每个子列表调用一次 rm。这比这个功能等效的版本更有效:

查找 /path -type f -exec rm '{}' \;

我知道 find 有“exec”标志。我只是从另一个资源中引用一个说明性的例子。

4

13 回答 13

459

仅当您的输入中没有空格时,以下内容才有效:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

从手册页:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.
于 2008-10-13T22:32:42.510 回答
275

在我看来,此页面上的所有现有答案都是错误的,包括标记为正确的答案。这是因为问题措辞含糊不清。

总结:   如果你想“每行输入只执行一次”命令,将整行(不带换行符)作为单个参数传递给命令,那么这是最好的 UNIX 兼容方式:

... | tr '\n' '\0' | xargs -0 -n1 ...

如果您使用 GNUxargs并且不需要与所有其他 UNIX(FreeBSD、Mac OS X 等)兼容,那么您可以使用 GNU-specific 选项-d

... | xargs -d\\n -n1 ...

现在进行冗长的解释……</p>


使用 xargs 时需要考虑两个问题:

  1. 它如何将输入拆分为“参数”;和
  2. 一次传递子命令的参数数量。

为了测试 xargs 的行为,我们需要一个实用程序来显示它被执行了多少次以及有多少个参数。我不知道是否有一个标准的实用程序可以做到这一点,但我们可以很容易地在 bash 中对其进行编码:

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

假设您将其保存为show当前目录并使其可执行,它的工作原理如下:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

现在,如果最初的问题真的是关于上面的第 2 点(我认为是这样,在阅读了几次之后)并且应该像这样阅读(更改为粗体):

如何让 xargs 对给定的每个输入参数只执行一次命令?它的默认行为是将输入分成参数并尽可能少地执行命令,将多个参数传递给每个实例。

那么答案是-n 1

让我们比较一下 xargs 的默认行为,它将输入拆分为空格并尽可能少地调用命令:

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

及其行为-n 1

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

另一方面,如果最初的问题是关于第 1 点的输入拆分,并且应该像这样阅读(许多来到这里的人似乎认为是这种情况,或者混淆了这两个问题):

我怎样才能让 xargs为给定的每一行输入只使用一个参数来执行命令?它的默认行为是将空格周围的行分块。

那么答案就更微妙了。

有人会认为这-L 1可能会有所帮助,但事实证明它不会改变参数解析。它只为每个输入行执行一次命令,参数与该输入行上的参数一样多:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

不仅如此,如果一行以空格结尾,它会附加到下一行:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

显然,-L这不是要改变 xargs 将输入拆分为参数的方式。

以跨平台方式(不包括 GNU 扩展)这样做的唯一参数是-0,它将输入拆分为 NUL 字节。

然后,只需借助以下方法将换行符转换为 NUL 即可tr

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

现在参数解析看起来没问题,包括尾随空格。

最后,如果您将此技术与 结合使用,则-n 1无论您有什么输入,每个输入行都会执行一个命令,这可能是查看原始问题的另一种方式(可能是最直观的,给定标题):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 

如上所述,如果您使用的是 GNU ,则xargs可以将 替换tr为 GNU 特定的选项-d

$ echo $'one \ntwo\nthree and four' | xargs -d\\n -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 
于 2015-03-02T10:03:01.697 回答
23

如果您想为来自的每一行(即结果)运行命令find,那么您需要什么xargs

尝试:

find 路径 -type f -exec 你的命令 {} \;

其中文字{}被文件名替换,并且\;需要文字find才能知道自定义命令在那里结束。

编辑:

(在编辑您的问题后澄清您所知道的-exec

来自man xargs

-L max- lines 每个命令行最多
使用max-lines 个非空白输入行。尾随空白导致输入行在下一个输入行逻辑上继续。暗示-x。

请注意,如果您使用以空格结尾的文件名,则会给您带来麻烦xargs

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

因此,如果您不关心该-exec选项,则最好使用-print0and -0

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a
于 2008-10-13T22:50:22.007 回答
21

如何让 xargs 对给定的每一行输入只执行一次命令?

-L 1是一个简单的解决方案,但如果任何文件中包含空格,它就不起作用。这是 find-print0参数的一个关键功能——用 '\0' 字符而不是空格分隔参数。这是一个例子:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

更好的解决方案是使用tr将换行符转换为 null ( \0) 字符,然后使用xargs -0参数。这是一个例子:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

如果您随后需要限制调用次数,您可以使用-n 1参数为每个输入调用一次程序:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

这还允许您在将中断转换为空值之前过滤 find 的输出。

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar
于 2015-05-07T14:37:47.473 回答
15

这两种方法也有效,并且适用于不使用 find 的其他命令!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

示例用例:

find . -name "*.pyc" | xargs -i rm '{}'

即使 pyc 文件包含空格,也会删除该目录下的所有 pyc 文件。

于 2014-08-15T00:49:07.017 回答
11

另一种选择...

find /path -type f | while read ln; do echo "processing $ln"; done
于 2012-12-18T09:58:38.677 回答
9
find path -type f | xargs -L1 command 

是你所需要的全部。

于 2008-10-13T23:14:31.193 回答
4

以下命令将在其中找到所有文件(-type f)/path,然后将它们复制cp到当前文件夹。请注意使用 if-I %在命令行中指定占位符字符,cp以便可以将参数放在文件名之后。

find /path -type f -print0 | xargs -0 -I % cp % .

用 xargs (GNU findutils) 4.4.0 测试

于 2009-06-04T05:23:25.623 回答
2

您可以分别使用 --max-lines 或 --max-args 标志来限制行数或参数(如果每个参数之间有空格)。

  -L max-lines
         Use at most max-lines nonblank input lines per command line.  Trailing blanks cause an input line to be logically continued on the next  input
         line.  Implies -x.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.
于 2008-10-13T22:40:29.687 回答
1

@Draemon 的答案似乎与“-0”是正确的,即使文件中有空格也是如此。

我正在尝试 xargs 命令,我发现“-0”与“-L”完美配合。甚至空格也被处理(如果输入为空终止)。以下是一个例子:

#touch "file with space"
#touch "file1"
#touch "file2"

以下将拆分空值并对列表中的每个参数执行命令:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

如果与“-0”一起使用, so-L1将对每个以空字符结尾的字符执行参数。要查看差异,请尝试:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

即使这将执行一次:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

该命令将执行一次,因为“-L”现在不会在空字节上拆分。您需要同时提供“-0”和“-L”才能工作。

于 2018-12-13T04:25:16.727 回答
0

似乎我没有足够的声誉来为上面的 Tobia 的答案添加评论,所以我添加了这个“答案”来帮助我们这些想要xargs在 Windows 平台上尝试相同方式的人。

这是一个 Windows 批处理文件,它与 Tobia 的快速编码的“显示”脚本执行相同的操作:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.
于 2016-03-05T21:42:09.850 回答
-3

在您的示例中,将 find 的输出通过管道传输到 xargs 的目的是 find 的 -exec 选项的标准行为是为每个找到的文件执行一次命令。如果您正在使用 find,并且您想要它的标准行为,那么答案很简单 - 不要使用 xargs 开始。

于 2008-10-13T22:49:16.563 回答
-3

在当前或子文件夹的每个 build.xml 上执行 ant task clean-all。

find . -name 'build.xml' -exec ant -f {} clean-all \;
于 2010-04-15T14:38:59.160 回答