假设我想删除目录中除名为“notes.txt”的文件之外的所有文件。我会使用管道执行此操作。ls | grep -v "notes.txt" | xargs rm
如果第二个管道的输出是 rm 应该使用的输入,为什么我需要 xargs?
为了便于比较,管道echo "#include <knowledge.h>" | cat > foo.c
将回显的文本插入到文件中,而无需使用 xargs。这两个管道有什么区别?
答案1
您混淆了两种截然不同的输入:STDIN 和参数。参数是命令启动时提供给命令的字符串列表,通常是在命令名称后指定它们(例如echo these are some arguments
或rm file1 file2
)。另一方面,STDIN 是命令启动后可以(可选)读取的字节流(有时是文本,有时不是)。以下是一些示例(请注意 可以cat
接受参数或 STDIN,但对它们的作用不同):
echo file1 file2 | cat # Prints "file1 file2", since that's the stream of
# bytes that echo passed to cat's STDIN
cat file1 file2 # Prints the CONTENTS of file1 and file2
echo file1 file2 | rm # Prints an error message, since rm expects arguments
# and doesn't read from STDIN
xargs
可以被认为是将 STDIN 样式的输入转换为参数:
echo file1 file2 | cat # Prints "file1 file2"
echo file1 file2 | xargs cat # Prints the CONTENTS of file1 and file2
echo
实际上或多或少做了相反的事情:它将其参数转换为 STDOUT(可以通过管道传输到其他命令的 STDIN):
echo file1 file2 | echo # Prints a blank line, since echo doesn't read from STDIN
echo file1 file2 | xargs echo # Prints "file1 file2" -- the first echo turns
# them from arguments into STDOUT, xargs turns
# them back into arguments, and the second echo
# turns them back into STDOUT
echo file1 file2 | xargs echo | xargs echo | xargs echo | xargs echo # Similar,
# except that it converts back and forth between
# args and STDOUT several times before finally
# printing "file1 file2" to STDOUT.
答案2
cat
接受输入STDIN
,但rm
不接受。对于此类命令,您需要xargs
逐行迭代STDIN
并使用命令行参数执行命令。
答案3
xargs
通过最小的例子来理解
xargs
在研究 xargs 为什么有用之前,让我们首先通过一些简单的例子来确保我们理解它的作用。
当您执行以下任一操作时:
printf '1 2 3 4' | xargs rm
printf '1\n2\n3\n4' | xargs rm
xargs 解析来自 stdin 的输入字符串,并用空格分隔参数,有点像 Bash,但细节略有不同。特别是,如果您使用xargs -L
而不是,空格和换行符的处理方式会有所不同-n
:https://stackoverflow.com/questions/6527004/why-does-xargs-l-yield-the-right-format-while-xargs-n-doesnt/6527308#6527308
因为我们没有使用-L
,所以上述两个调用是等效的,并且 xargs 将解析出四个参数:1
、2
和。3
4
然后,xargs 会获取解析出来的参数,并将它们提供给我们要调用的程序。在我们的例子中,它是可执行文件/usr/bin/rm
。
默认情况下,xargs 不会指定每次要传递多少个参数,除非我们传递一些标志,否则可能会传递多个参数。因此,上述xargs
调用可能等同于:
rm 1 2 3 4
或者:
rm 1 2
rm 3 4
或者:
rm 1
rm 2
rm 3
rm 4
并且我们通常不知道上述哪种情况发生了,因为对于rm
,最终结果是相同的:文件1
、2
、3
和4
将被删除,所以我们不太关心哪一个xargs
在做什么,所以我们只是让它做它的事情。
这可能会对其他程序产生影响,例如/usr/bin/echo
,每次调用都会添加一个换行符。
控制一次传递多少个参数
xargs
我们可以使用某些标志来控制一次传递多少个参数。
最简单的一个是-n
,它限制一次传递的最大参数数量。
/usr/bin/echo
然后,我们可以尝试使用而不是 来观察发生了什么/usr/bin/rm
,因为echo
不同于 ,rm
它对 的处理方式echo 1 2
不同,echo 1; echo 2
因为它会为每个调用添加一个换行符。
考虑到这一点,如果我们运行:
printf '1 2 3 4' | xargs -n2 echo
它每次提供 2 个参数echo
,相当于:
echo 1 2
echo 3 4
生成结果:
1 2
3 4
如果我们运行:
printf '1 2 3 4' | xargs -n1 echo
它每次提供一个参数echo
,相当于:
echo 1
echo 2
echo 3
echo 4
生成结果:
1
2
3
4
另一种方式是使用-L
而不是-n
。但只-L
用-n
换行符分隔,而不是空格:https://stackoverflow.com/questions/6527004/why-does-xargs-l-yield-the-right-format-while-xargs-n-doesnt/6527308#6527308
控制参数数量的另一种常见方式是-I
暗示-L1
,例如:
printf '1\n2\n3\n4\n' | xargs -I% echo a % b
相当于:
echo a 1 b
echo a 2 b
echo a 3 b
echo a 4 b
因此产生:
a 1 b
a 2 b
a 3 b
a 4 b
替代方法及其xargs
优越性
现在我们了解了什么xargs
是可行的,让我们考虑一下替代方案以及为什么xargs
更好。
假设我们有一个文件:
注释.txt
1
2
3
4
代替:
xargs < notes.txt | rm
我们可能想要使用:
rm $(cat notes.txt)
扩展为:
rm 1 2 3 4
然而,这是有问题的,因为Linux 程序的命令行参数有一个最大大小所以如果中的参数太多,它可能会失败notes.txt
。
xargs
知道这一点,并自动明智地拆分参数,以避免同时出现太多参数。
而且,像 stdin 这样的流没有最大大小限制,因此可以像这样以任意大小运行。它之所以有效,是因为可以使用系统read()
调用一点一点地读取流,而 CLI 参数必须一次性全部加载到虚拟内存中,因此不需要对流大小进行硬性限制。
您可以尝试的另一种简单方法是:
while IFS="" read -r p || [ -n "$p" ]
do
rm "$p"
done < notes.txt
从:https://stackoverflow.com/questions/1521462/looping-through-the-content-of-a-file-in-bash但这需要大量的输入,并且可能会更慢,因为:
- 它对
/usr/bin/rm
每个参数调用一次可执行文件,而不是对一堆参数调用更少的次数 bash
while
与 C 代码相比,循环xargs
花费了更多时间
xargs
更有趣的是,GNU 版本提供了-P
并行操作选项!
有关的:https://unix.stackexchange.com/questions/24954/when-is-xargs-needed