何时应该使用xargs而不是while-read循环?

31

xargs在shell脚本中被广泛使用;通常可以使用while read -r; do ... donewhile read -ar; do ... done循环在bash中重新构造这些用法。

何时应该优先考虑使用xargs,何时应该优先考虑使用while-read循环?


我很惊讶没有人提到 xargs 的优点是独立于所使用的 shell,而我从来不确定在一个 shell 中有效的语法是否在另一个 shell 中同样有效。 - bers
6个回答

34
while循环的问题在于它们倾向于逐个处理项目,有时是不必要的。这就是xargs的优点所在 - 它可以批量处理参数,让一个命令处理大量项目。

例如,一个while循环:

pax> echo '1
2
3 
4
5' | while read -r; do echo $REPLY; done
1
2
3
4
5

以及相应的 xargs

pax> echo '1
2
3 
4
5' | xargs echo
1 2 3 4 5

你可以看到,这里通过使用whilexargs分别逐行和一次性处理所有行。换句话说,前者相当于echo 1 ; echo 2 ; echo 3 ; echo 4 ; echo 5,而后者相当于echo 1 2 3 4 5(五个进程与一个进程相比)。在处理数千或数万行时,这真的会有很大的区别,因为创建进程需要时间。

当使用能够接受多个参数的命令时,大多数情况下都是有利的,因为它减少了启动单个进程的数量,使得事情更快速。

当我处理小文件或要运行的命令对每个项目都很复杂(我懒得编写单独的脚本给xargs),我会使用while变体。

当我关注性能(大文件)时,即使我必须编写单独的脚本,我也会使用xargs


10
我知道这是一个旧帖子,但我想补充一下,xargs -n1 命令可以得到与 while 循环相同的结果。 - bendaizer

7

一些xargs的实现还可以理解一个-P MAX-PROCS参数,它允许xargs并行运行多个作业。使用while read循环模拟这种情况会相当困难。


3
请注意,标准输出的结果可能不可信。请参考示例:http://www.gnu.org/software/parallel/man.html#differences_between_xargs_and_gnu_parallel(并发 grep 部分)。 - Ole Tange

6

GNU Parallel http://www.gnu.org/software/parallel/具有xargs(使用-m)的优点和以换行符作为分隔符的while-read的优点,还有一些新功能(例如输出分组,远程计算机上作业的并行运行和上下文替换)。

如果您安装了GNU Parallel,则我看不到您会使用xargs的任何情况。而我只会在执行块太大而无法放在单行中时(例如,如果它包含if语句或类似内容),并且您拒绝创建bash函数的情况下使用read-while

对于所有小型脚本,我实际上发现使用GNU Parallel更易读。paxdiablo的示例:

echo '1
2
3 
4
5' | parallel -m echo

使用GNU Parallel将WAV文件转换为MP3:

find sounddir -type f -name '*.wav' | parallel -j+0 lame {} -o {.}.mp3

观看GNU Parallel的介绍视频:http://www.youtube.com/watch?v=OpaiGYxkSuQ


5
"

xargs"有一个选项“-n max-args”,我猜想这将允许一次性为多个参数调用命令(对于“grep”、“rm”和许多其他类似程序非常有用)。 尝试从手册页面中获取示例:

"
cut -d: -f1 < /etc/passwd | sort | xargs -n 5 echo

你会看到它每行“回显”了5个用户

P.S. 别忘了,“xargs”是一个程序(就像子shell)。因此,没有简单的方法将信息传递给你的shell脚本(你需要读取“xargs”的输出并以某种方式解释来填充你的shell/env变量)。


1
相反,有些情况下你会有一个文件列表,每行一个,其中包含空格。例如来自于 findpkgutil 等。要使用 xargs,你需要首先使用 sed 将行用引号括起来,但这看起来很笨重。
使用 while 循环,脚本可能更容易阅读/编写。并且带有空格的参数的引用是微不足道的。下面的示例是人工的,但请想象从除了 find 之外的其他地方获取文件列表...
function process {
  while read line; do
    test -d "$line" && echo "$line"
  done
}

find . -name "*foo*" | process

1
不好意思,这句话无法翻译成有意义的中文。它似乎是一段程序代码或命令,但缺少上下文和背景信息,无法准确理解其含义。请提供更多信息以便我能够为您提供正确的翻译。 - CervEd
要处理find命令的输出,你可以使用它的-exec选项或者使用空字符代替换行符,使用find <something> -print0 | xargs -0 …命令。这种方法的优点是可以正确处理文件名中的换行符。 - Arch Stanton

1
我不明白,人们一直在喋喋不休地谈论while必须在循环内部而不是循环外部执行。我对Linux了解甚少,但我知道使用MS-DOS的变量构建参数列表或>文件,cmd<文件构建参数列表如果超过行长度限制是相当简单的。
还是人们说Linux不如MS-DOS好?(见鬼,我知道你可以构建链,因为许多Bash脚本显然正在这样做,只是没有在循环中)。
此时,它成为内核限制/首选项问题。xargs并不神奇;管道处理优于字符串构建(嗯,MS-DOS;您可以使用“指针”构建字符串并避免任何复制(毕竟这是虚拟内存,除非您更改数据,否则可以跳过字符串连接的开销...但管道是更本地的支持)。实际上,我认为我不能给它并行处理的优势,因为您可以轻松地创建几个任务循环以查看切片数据(如果您避免复制,则是一个非常快速的操作)。
最终,xargs更适用于内联命令,速度优势微不足道(编译/解释字符串构建之间的差异),因为它所做的一切都可以通过shell脚本完成。

通常不会使用这两种形式来构建参数样式列表,而是进行流处理,其中xargs的速度优势可能非常大。Bash(以及其他一些shell,但不包括标准的POSIX Bourne shell)允许使用数组来实现您所描述的功能。 - Charles Stewart

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接