Linux xargs 命令解析

基本使用

xargs 是 Linux 系统中的一个命令行工具,它可以读取标准输入并将其作为参数构建新的命令并执行。
xargs 可以帮助 echormmkdir 等命令接收标准输入作为参数。

1
2
3
4
$ xargs mkdir
test1 test2
$ ls
test1 test2

比如执行 xargs mkdir 命令,输入 test1 test2 后回车,再按 CTRL-D 结束输入,等效于直接执行 mkdir test1 test2 命令。
xarg 读取了标准输入中的 test1 test2,并将它们作为参数传递给 mkdir 命令,组合成一个完整的 mkdir test1 test2 命令并执行。

实际上 xargs 很少用在上述交互式场景中,更多的是搭配管道符 |,通过前一个命令的输出构建新的命令。

1
2
3
$ echo test3 test4 test5 | xargs mkdir
$ ls
test1 test2 test3 test4 test5

|| xargs 的区别:

  • 管道符 | 是将左边命令的输出结果作为右边命令的输入值
  • | xargs 则可以将左边命令的输出结果作为右边命令的参数选项

find 与 xargs

xargs 最常见的搭配应该就是 find 命令了。即通过 find 查找特定的文件或目录,再将结果传递给 xargs,对找到的结果执行特定的操作。

如删除 /tmp 路径下最近两周内未做改动的文件:
find /tmp -type f -mtime +14 | xargs rm

有一点特别需要注意:默认情况下,xargs 从标准输入读取命令参数时,会以空格作为分隔符来识别多个选项。
而文件和目录的名字有时候也会包含空格,导致一个文件名被 xargs 识别为两个参数。

1
2
3
4
5
6
7
8
$ mkdir test\ 6
$ ls
'test 6' test1 test2 test3 test4 test5
$ find . -type d | xargs rmdir
rmdir: failed to remove './test': No such file or directory
rmdir: failed to remove '6': No such file or directory
$ ls
'test 6'

更安全一点的做法是使用 -0 选项。-0 选项可以指定 xargs 在读取标准输入时使用 null 作为分隔符。而 find 命令的 -print0 选项同样可以将输出指定为使用 null 进行分割。

即将前面的命令替换为 find . -type d -print0 | xargs -0 rmdir

find 搭配 -exec 选项和搭配 xargs 命令的区别

find 命令可以使用其 -exec 选项对查找到的结果执行特定的操作。同样的需求 xargs 也可以实现。

比如需要删除当前目录下所有的 TXT 文件:

  • 使用 -execfind . -type f -name "*.txt" -exec rm {} \;
  • 使用 xargsfind . -type f -name "*.txt" | xargs rm

实际上两者的执行效率存在着很大的差距。
比如使用 for i in {1..100}; do touch $i.txt; done 命令创建 100 个 TXT 文件,再分别使用上述两个命令删除这些文件(用 time 命令计时),具体的效率如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
$ for i in {1..100}; do touch $i.txt; done
$ ls
1.txt 18.txt 27.txt 36.txt 45.txt 54.txt 63.txt 72.txt 81.txt 90.txt
10.txt 19.txt 28.txt 37.txt 46.txt 55.txt 64.txt 73.txt 82.txt 91.txt
100.txt 2.txt 29.txt 38.txt 47.txt 56.txt 65.txt 74.txt 83.txt 92.txt
11.txt 20.txt 3.txt 39.txt 48.txt 57.txt 66.txt 75.txt 84.txt 93.txt
12.txt 21.txt 30.txt 4.txt 49.txt 58.txt 67.txt 76.txt 85.txt 94.txt
13.txt 22.txt 31.txt 40.txt 5.txt 59.txt 68.txt 77.txt 86.txt 95.txt
14.txt 23.txt 32.txt 41.txt 50.txt 6.txt 69.txt 78.txt 87.txt 96.txt
15.txt 24.txt 33.txt 42.txt 51.txt 60.txt 7.txt 79.txt 88.txt 97.txt
16.txt 25.txt 34.txt 43.txt 52.txt 61.txt 70.txt 8.txt 89.txt 98.txt
17.txt 26.txt 35.txt 44.txt 53.txt 62.txt 71.txt 80.txt 9.txt 99.txt
$ time find . -type f -name "*.txt" -exec rm {} \;
find . -type f -name "*.txt" -exec rm {} \; 0.05s user 0.02s system 104% cpu 0.060 total
$ for i in {1..100}; do touch $i.txt; done
$ time find . -type f -name "*.txt" | xargs rm
find . -type f -name "*.txt" 0.00s user 0.00s system 80% cpu 0.001 total
xargs rm 0.00s user 0.00s system 94% cpu 0.003 total

前者是 0.06,后者是 0.004。使用 xargs 的执行效率更高。

输出执行的命令

-t 选项可以把 xargs 拼接后执行的命令打印到终端窗口中。方便对脚本进行调试。

1
2
$ echo test1 test2 test3 | xargs -t mkdir
mkdir test1 test2 test3

输出命令并提示用户确认

-p 选项可以把 xargs 拼接后执行的命令打印出来,并等待用户进行确认。

1
2
$ ls | xargs -p rmdir
rmdir test1 test2 test3 ?...y

执行多个命令

借助 -I 选项可以令 xargs 同时执行多个命令。

1
2
3
4
5
6
7
8
9
10
$ cat foo.txt
one
two
three
$ cat foo.txt | xargs -I % sh -c 'echo %; mkdir %'
one
two
three
$ ls
foo.txt one three two

常用实例

查找当前路径下所有的 PNG 图片并将它们归档到 images.tar.gz 压缩包中
$ find . -name "*.png" -type f -print0 | xargs -0 tar -cvzf images.tar.gz

获取当前系统中所有用户的用户名,以单行列表的形式输出

1
2
$ cut -d: -f1 < /etc/passwd | sort | xargs echo
_apt backup bin daemon games gnats irc landscape list lp mail man messagebus news nobody pollinate postgres proxy root sshd starky sync sys syslog systemd-network systemd-resolve systemd-timesync tcpdump tss uucp uuidd www-data

删除当前路径下名为 no_use 的文件
$ find . -name "no_use" -type f -print0 | xargs -0 rm -v -f

复制一个文件到多个路径下

1
2
3
4
5
$ ls
dest1 dest2 test_file
$ echo ./dest1/ ./dest2/ | xargs -n 1 cp -v ./test_file
'./test_file' -> './dest1/test_file'
'./test_file' -> './dest2/test_file'

上面例子中 xargs-n 1 选项非常重要。-n 选项用于指定 xargs 在将标准输入作为参数与命令拼接在一起时,参数的最大长度。

简单来说,当 -n 为 1 时,标准输入中以空格分割的每一项都与命令进行拼接,最终形成多条命令;当不存在 -n 1 时,标准输入中以空格分割的所有参数项直接与命令进行拼接,形成一条命令并执行。

1
2
3
4
5
6
$ echo ./dest1/ ./dest2/ | xargs -n 1 -t cp ./test_file
cp ./test_file ./dest1/
cp ./test_file ./dest2/
$ echo ./dest1/ ./dest2/ | xargs -t cp ./test_file
cp ./test_file ./dest1/ ./dest2/
cp: -r not specified; omitting directory './dest1/'

  • echo ./dest1/ ./dest2/ | xargs -n 1 cp ./test_file 等效于 cp ./test_file ./dest1/cp ./test_file ./dest2/ 两条命令
  • echo ./dest1/ ./dest2/ | xargs cp ./test_file 等效于 cp ./test_file ./dest1/ ./dest2/ 一条命令(语法是错的)

参考资料

Linux and Unix xargs command tutorial with examples
12 Practical Examples of Linux Xargs Command for Beginners