xargs 用来把一个命令的结果传递给另外一个命令执行。这是一个日常中经常会遇到的场景。

xargs 会从标准输入读取内容,然后将内容送给其他命令构建其他可执行命令。这意味着可以从一个命令行的输出结果读取内容并作为另一个命令的输入。

xargs 默认读取时按照空白字符分割的输入,输入可以带双引号,单引号,或者反斜杠转义,xargs 也可以读取新行,然后将输入作为参数执行对应的命令一次或者多次,默认是 /bin/echo。空白行输入会被忽略。

Unix 文件系统的文件可以包含空白和新行,这个默认的行为可能造成一些问题,包含空白的文件名可能被 xargs 错误读取。在这个情况下,最好是使用 -0 选项,在使用这个选项之前,同样要保证输出的结果同样是 null 字符分割的字符串,比如 GNU 下 find 命令的 -print0 选项。

如果任何调用产生 255 状态,xargs 会立即结束输入并给出错误。

使用

常用的选项

-o, --null  输入是 null 分割
-a file     从文件读取输入
-d delim    自定义分隔符
-E eof-str  自定义结束字符串
-I replace-str  使用自定义占位符

-t          在命令执行之前打印
-p          交互模式,每一次执行命令会进行确认

查找并删除

删除目录下特定 PATTERN 的文件:

find /tmp -name core -type f -print | xargs /bin/rm -f
find /tmp -name core -type f -print0 | xargs -0 /bin/rm -f    # name 可以包含空白
# or not use xargs
find /tmp -depth -name core type f -delete

删除目录下除了固定格式的其他文件

find . -type f -not -name '*.gz' -print0 | xargs -0 -I {} rm -v {}

说明:

  • -I {} xargs 的 -I {} 属性会将后面命令中的内容替换为 -I {} 指定的内容

xargs 执行多个命令

通常情况下 xargs 只会在后面使用一个命令比如说 rm -v {} ,但如果想要在 xargs 后接多个命令则可以使用

< a.txt xargs -I {} sh -c 'echo {}; echo {};'

这样就可以执行多个命令。1

从文件读取

比如文件 links.txt 中每一行都是一个链接,那么可以使用该命令将所有链接下载下来

xargs -a links.txt -I {} wget {}

找出目录下的 png 图片并打包到一个压缩包

首先找出目录下的 png 图片路径,然后打包到一个文件

find /path/to -name "*.png" -type f -print0 | xargs -0 tar czvf photos.tar.gz

解释:

  • -print0 的选项输出文件完整路径,然后紧跟一个空字符 (null), 而不是默认 -print 选项使用 newline ,该选项使得其他命令可以解析 find 命令的输出,比如 xargs-0 选项
  • 同上一条 -0 选项,表示的是输入的内容用 null 字符来分割,而不是使用空白字符,xargs 在使用该选项时会按照字面接受参数

将 ls 结果输出到一行

将 ls 结果输出到一行

ls -1 | xargs

说明:

  • ls -1 注意这里是 -1 是数字的 1,不是英文的 L,会一行输出一个文件

自定义定界符

通常情况下 xargs 会使用空格或者空行来分割字符,这也就意味着如果文件名中包含空格,那么在处理时会遇到问题,所以可以使用 -d delim 来指定自己的定界符 (delimiter)。或者使用 xargs -0

输出当前系统中的账号

cut -d: -f1 < /etc/passwd | sort | xargs

说明:

  • -d: 表示按照 : 分割
  • -f1 表示取第一个字段(第一列)

批量重命名

以前一直以为批量重命名得是一个非常高级的功能,在 GNU 下通过 xargs 和 rename 就可以快速实现

find -depth /path/to | xargs -n 1 rename -v 's/origin/after/' {} \;

该命令会把目录下所有文件统一成小写。

说明:

  • rename 使用 perl 的正则

接受多个参数

通常情况下 xargs 只会接受一个参数作为命令的输入,如果有多个参数时,可以使用如下方法:

echo "one" "two" "three" | xargs -l sh -c 'echo $0, $1, $2' | xargs

批量替换标签

我一直使用纯文本(markdown)文件来记笔记,所以历史中存在很多没有好好管理的标签,比如有一些笔记使用了 # 加一些中文来标记,这部分内容我想进行管理,批量替换,结合 ripgrepxargssed

rg "#要替换的标签" -l |xargs -I {} sed -i 's/#要替换的标签/#之后的标签/g' {}

并行执行命令

通过 xargs 的 man 可以知道

This manual page documents the GNU version of xargs. xargs reads items from the standard input, delimited by blanks (which can be protected with double or single quotes or a backslash) or newlines, and executes the command (default is /bin/echo) one or more times with any initial- arguments followed by items read from standard input. Blank lines on the standard input are ignored.

这意味这 xargs 会等待,并收集所有的输出之后再执行后面的命令。这并不是我们想要的,所以仔细研读 man 文档之后我们会发现 -n 选项和 -P 选项。

printf %s\\n {0..99} | xargs -n 1 -P 8 -I {} sh -c 'echo {}; sleep 2;'

说明:

  • -n 选项会执行每一次命令最多拿多少个参数
  • -P 选项会同时开启多少个 processes 来执行命令

这样就能解读这行命令,打印从 0 到 99 这样 100 个数,每次开启 8 个进程,每个命令接受一个参数,执行后停顿 2 秒。看输出就能知道 8 个数同时打印, sleep 2 秒之后继续。这样就做到了并发执行。2

reference