小经历
在我刚入行的时候,我的导师告诉我一条命令,可以在指定的目录下搜索特定的字符串。命令如下
find packages/apps/Launcher3 -name '*.xml' | xargs grep -n --color Launcher3
find 命令在 packages/apps/Launcher3
目录下搜索了所有的 XML 文件,xargs 命令通过管道读取了标准输入,也就是 find 命令的结果,然后把这些结果构建为 grep 命令的参数,最后执行 grep 命令。
从这个小例子可以看出,xargs 就像是一个胶水一样,粘合了两个不相关的命令。xargs 通常与 find 命令一起使用,但是有时候我们会看到一些难以理解的用法,本文就来答疑解惑。
文件名中有空白字符
在 Linux find 命令 这篇文章中,分析过一个问题,如果文件名中包含空白字符,那么 xargs 命令的默认处理行为会失败。可以通过 find 命令的 -print0
选项,把输出结果以空字符分割,然后通过 xargs 命令的 -0
(数字0,而不是字母o) 选项指定分割符为空字符,从而解决这个问题。命令如下
find . -name '*.bak' -print0 | xargs -0 rm
在子shell中执行命令
前面列出的两个例子,都可以使用 find 命令的 -exec
来代替,但是 xargs 命令更简洁一些。然而 xargs 命令不止于此,如果我们想在执行命令前配置环境或者对命令执行重定向,都可以通过 xargs 创建一个子 shell 来执行。
下面的命令会把当前目录下,所有以 .sh 结果的文件,复制到 bak 目录下。
find . -name \*.sh -print0 | xargs -r0 sh -c 'mv "$@" ./bak' move
xargs 的 -r 选项表示在标准输入为空时,不执行命令。
这个命令看起来非常的奇怪,我们一步一步来解析。
首先 sh -c
指定了一个命令 `mv "$@" ./bak`
,这个命令会在一个子 shell 环境中执行,之所以要用加上单引号,是为了防止 shell 展开。
然后,`mv "$@" ./bak`
命令中的 "#@"
是 shell 的位置参数,之所以要加双引号,是为了防止文件名中有空格或者换行符,这是 shell 脚本的知识点。在执行命令时,这个位置参数会展开为文件名,也就是 find 命令的结果。
最后,还有一个非常奇怪的 move
参数,这是什么意思呢? 这也与 shell 脚本的知识有关。位置参数的第0参数是表示程序的名字,mv 命令默认会忽略。但是 $@
会把第0个参数展开为一个文件名,这就会导致 mv 命令忽略第一个文件名参数。为了防止这中行为的发生,可以手动给第0个参数赋值,这也就是上面的命令末尾的move
。其实这个 move
只是充当一个占位的作用,它可以是任何字符串。
参考
https://www.gnu.org/software/findutils/manual/html_mono/find.html#Invoking-xargs