让 xargs 对每一行输入执行一次命令

Posted

技术标签:

【中文标题】让 xargs 对每一行输入执行一次命令【英文标题】:Make xargs execute the command once for each line of input 【发布时间】:2010-09-16 23:44:24 【问题描述】:

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

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

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

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

find /path -type f -exec rm '' \;

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

【问题讨论】:

在您提供的示例中,find /path -type f -delete 会更有效:) 尽量不要使用 xargs... OP,我知道这个问题已经很老了,但它仍然出现在谷歌上,恕我直言,接受的答案是错误的。请参阅下面的更长答案。 请考虑将您的接受切换为@Tobia 的答案,这样会更好。接受的答案不处理名称中的空格,并且不允许 xargs 命令的多个参数,这是 xargs 的主要功能之一。 【参考方案1】:

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

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.

【讨论】:

对我来说,它可以输出为xargs -n 1,因为您给出的那个显示“参数列表太长”。 如果省略MAX-LINES,则默认为1,因此xargs -l就足够了。见info xargs @Wernight:“-n1”不会为每个输入行提供 1 次调用。也许您的输入行太长了。演示:echo "foo bar" | xargs -n1 echo。因此,如果你输入像“ls”这样的东西,它就不能很好地处理空格。 这是错误的。 -L 1 不回答原始问题,-n 1 仅在一种可能的解释中回答。请参阅下面的长答案。 @Tobia:它回答了最初的问题,该问题非常具体地与输入行有关。这正是-L 1 所做的。对我来说,OP 似乎显然是在试图避免默认的分块行为,并且由于这是被接受的,我认为我是对的。您的答案解决了一个稍微不同的用例,您也希望分块行为。【参考方案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.

【讨论】:

【参考方案3】:

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

【讨论】:

实际上,我可以从 OP 的编辑中暗示输入数据与find 无关,这就是为什么他们不喜欢@ 987654322@ 选项。【参考方案4】:

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

试试:

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

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

编辑:

(在编辑您的问题后澄清您了解-exec

来自man xargs

-L 最大线数 每个命令行最多使用 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 选项,则最好使用-print0-0

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a

【讨论】:

我发现在命令前面加上 echo 'find 很有趣。 -type f -print0 | xargs -0L1 回显 wc -l'。然后,您可以轻松预览 xargs 将生成的命令,例如查看使用“-0L2”时的差异【参考方案5】:
find path -type f | xargs -L1 command 

就是你所需要的。

【讨论】:

【参考方案6】:

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

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

使用 xargs (GNU findutils) 4.4.0 测试

【讨论】:

【参考方案7】:

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

find . -name 'build.xml' -exec ant -f  clean-all \;

【讨论】:

不是每个人都安装了ant【参考方案8】:

另一种选择...

find /path -type f | while read ln; do echo "processing $ln"; done

【讨论】:

【参考方案9】:

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

xargs -I '' rm ''
xargs -i rm ''

示例用例:

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

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

【讨论】:

这会为每个非最佳元素发出一个实用程序调用。【参考方案10】:

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

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

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

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

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

现在是长篇大论……


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

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

要测试 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 对每个 argument 的输入只执行一次命令?它的默认行为是将输入分块尽可能少地执行命令,将多个参数传递给每个实例。 em>

那么答案是-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 执行命令 with 恰好 一个参数 给定的每一行输入?它的默认行为是将行围绕空格分块。

那么答案就更微妙了。

有人会认为-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 字节。

然后,只需在tr 的帮助下将换行符转换为 NUL:

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

【讨论】:

看起来这是更好的答案。但是,我还是不太明白 -L 和 -n 有什么区别……你能解释一下吗? @olala -L 每个输入行执行一次命令(但行尾的空格将其连接到下一行,并且该行仍然根据空格拆分为参数);而-n 对每个输入参数执行一次命令。如果您在输出示例中计算-> 的数量,则这些是脚本./show 执行的次数。 GNU xargs 可能有也可能没有有用的扩展,可以让你取消tr 它有一个非常有用的扩展;来自xargs --help - -d, --delimiter=CHARACTER 输入流中的项目由 CHARACTER 分隔,而不是由空格分隔;禁用引号和反斜杠处理以及逻辑 EOF 处理 @Tobia:您介意我编辑您的答案以包含xargs -d '\n' 作为适用于“某些”系统的tr '\n' '\0' | xargs -0 的快捷方式吗? @krlmlr 我加了。【参考方案11】:

如何让 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 将换行符转换为空(\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

【讨论】:

第二个代码块 tr '\n' '\0\ => tr '\n' '\0' 中存在语法错误,我尝试修复此问题,但“编辑必须位于至少 6 个字符”(这似乎和 git 拒绝提交一样愚蠢,因为我的更改少于 6 个字符) 这是什么意思:“使用-L 的另一个问题是它不允许每个xargs 命令调用有多个参数。” ? 我已经改进了我的答案以删除那些无关的信息@Moberg。【参考方案12】:

我似乎没有足够的声望给Tobia's answer above添加评论,所以我添加这个“答案”来帮助我们这些想要在Windows平台上以同样的方式尝试xargs的人。

这是一个 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.

【讨论】:

【参考方案13】:

@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

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

 #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”才能工作。

【讨论】:

以上是关于让 xargs 对每一行输入执行一次命令的主要内容,如果未能解决你的问题,请参考以下文章

xargs 将标准输入转换成命令行参数

exec与xargs区别

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

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

xargs的原理剖析及用法详解

xargs用法详解