-I vs -i
-i 默认占位符是{}, 而大写I 可以指定
新旧POSIX标准的不同
-i是1997年的POSIX标准中所支持的选项,而在2004年的POSIX标准中已被移除,所以尽可能的使用-I而不是-i。
另外,不同版本的xargs对-i的处理也有所不同,比如在当前的MacOS上的xargs,直接都没有-i参数,这也是脚本移植的时候需要考虑的内容。确认结果如下所示,可以看到相同的命令在MacOS上的xargs是无法使用的
liumiaocn:xargs liumiao$ sw_vers
ProductName:Mac OS X
ProductVersion:10.15.2
BuildVersion:19C57
liumiaocn:xargs liumiao$ find . -name "*.tar.gz" |xargs -i echo {}
xargs: illegal option -- i
usage: xargs [-0opt] [-E eofstr] [-I replstr [-R replacements]] [-J replstr]
[-L number] [-n number [-x]] [-P maxprocs] [-s size]
[utility [argument ...]]
liumiaocn:xargs liumiao$
-I使用说明
man的内容中的解释用词使用的是replace,而为了更好地帮助理解,-I用于指定占位符可能更容易接受。首先我们来看一下如下的使用示例。
事前准备
liumiaocn:easypack liumiao$ find . -name '*.tar.gz'
./k8s/shell/data/pause-amd64-3.1.tar.gz
./containers/alpine/redmine/themes.tar.gz
./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz
liumiaocn:easypack liumiao$
说明:用于提供输入信息,可以随便准备,比如直接显示某个目录下的所有文件也可以
使用方法:通过-I进行显示
xargs缺省使用echo进行显示,这里指定-I 进行显示
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs -I {} echo {}
./k8s/shell/data/pause-amd64-3.1.tar.gz
./containers/alpine/redmine/themes.tar.gz
./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz
liumiaocn:easypack liumiao$
和缺省xargs动作的比较
由于此处演示使用的是echo命令,和缺省的xargs动作几乎一致
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs
./k8s/shell/data/pause-amd64-3.1.tar.gz ./containers/alpine/redmine/themes.tar.gz ./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz
liumiaocn:easypack liumiao$
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs echo
./k8s/shell/data/pause-amd64-3.1.tar.gz ./containers/alpine/redmine/themes.tar.gz ./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz
liumiaocn:easypack liumiao$
占位符的理解
通过如下的执行,可以更加清楚地理解占位符的意义,首先,指定-I 之后我们不传给echo命令,可以看到显示为三个空行
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs -I {} echo
liumiaocn:easypack liumiao$
我们在结果输出显示的文件名的前后分别加上hello和liumiao的字符串,可以使用如下命令即可简单做到
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs -I {} echo hello {} liumiao
hello ./k8s/shell/data/pause-amd64-3.1.tar.gz liumiao
hello ./containers/alpine/redmine/themes.tar.gz liumiao
hello ./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz liumiao
liumiaocn:easypack liumiao$
由此扩展可以将占位符指定为多于一个参数的指定位置,比如mv 和cp等常用命令,这里不再赘述。
通过-I指定占位符
-i缺省使用{}作为占位符,而新的POSIX标准中占位符可以通过-I进行指定,使用示例如下所示:
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs -I @ echo hello @ liumiao
hello ./k8s/shell/data/pause-amd64-3.1.tar.gz liumiao
hello ./containers/alpine/redmine/themes.tar.gz liumiao
hello ./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz liumiao
liumiaocn:easypack liumiao$
liumiaocn:easypack liumiao$ find . -name '*.tar.gz' |xargs -I @@ echo hello @@ liumiao
hello ./k8s/shell/data/pause-amd64-3.1.tar.gz liumiao
hello ./containers/alpine/redmine/themes.tar.gz liumiao
hello ./containers/standard/rancher/k3s/ansible/download/files/pause-amd64-3.1.tar.gz liumiao
liumiaocn:easypack liumiao$
管道“|” 用来将前一个命令的标准输出传递到下一个命令的标准输入。
xargs 将前一个命令的标准输出传递给下一个命令,作为它的参数。
可见,标准输入与命令参数是不同的。个人理解,命令参数就是直接跟在命令后面的,标准输入可以是键盘,文件等。
所以,管道符 | 所传递给程序的不是简单地在程序名后面输入的参数,它们会被程序内部的读取功能如scanf和gets等接收,而xargs则是将内容作为普通的参数传递给程序,相当于直接跟在命令后面。况且,有些命令是不接受标准输入的,比如kill,rm等命令。
总结:管道符后不加xargs相当于先将xargs后面的命令回车执行一下再从键盘里输入管道符前面命令执行的结果内容
加上xargs 相当于执行命令(xargs后面的命令)后,直接从键盘输入管道符前面命令执行的结果内容再回车,即管道前面的结果直接跟在xargs 后面的命令后面
再总结一下,就是回车的先后顺序不太一样。
-exec:{}表示命令的参数即为所找到的文件,以;表示comman命令的结束。\是转义符, 因为分号在命令中还有它用途,所以就用一个\来限定表示这是一个分号而不是表示其它意思。
-ok: 和 -exec 的作用相同,格式也一样,只不过以一种更为安全的模式来执行该参数 所给出的shell给出的这个命令之前,都会给出提示,让用户来确定是否执行。
#xargs将参数一次传给echo,即执行:echo begin ./xargs.txt ./args.txt
find . -name '*.txt' -type f | xargs echo begin
#exec一次传递一个参数,即执行:echo begin ./xargs.txt;echo begin ./args.txt
find . -name '*.txt' -type f -exec echo begin {} \;
是否可以指定占位符
-i使用缺省的占位符{},而-I是可以指定的,所以在使用上也显示有不同
xargs 要结合管道来完成 格式:find [option] express |xargs command 很明显,exec是对每个找到的文件执行一次命令,除非这单个的文件名超过了几k,否则不 会出现命令行超长出报错的问题。 而xargs是把所有找到的文件名一股脑的转给命令。当文件很多时,这些文件名组合成的命 令行参数很容易超长,导致命令出错。
另外, find | xargs 这种组合在处理有空格字符的文件名时也会出错,因为这时执行的命令 已经不知道哪些是分割符、哪些是文件名中的空格! 而用exec则不会有这个问题。
相比之下,也不难看出各自的缺点 1、exec 每处理一个文件或者目录,它都需要启动一次命令,效率不好; 2、exec 格式麻烦,必须用 {} 做文件的代位符,必须用 ; 作为命令的结束符,书写不便。
3、xargs 不能操作文件名有空格的文件;
4、exec参数是一个一个传递的,传递一个参数执行一次命令;xargs一次将参数传给命令,可以使用-n控制参数个数
5、exec文件名有空格等特殊字符也能处理;xargs不能处理特殊文件名,如果想处理特殊文件名需要特殊处理
综上,如果要使用的命令支持一次处理多个文件,并且也知道这些文件里没有带空格的文件, 那么使用 xargs比较方便; 否则,就要用 exec了。
xargs是 Unix 系统的一个很有用的命令,但是常常被忽视,很多人不了解它的用法。
本文介绍如何使用这个命令。
Unix 命令都带有参数,有些命令可以接受"标准输入"(stdin)作为参数。
$ cat /etc/passwd | grep root
上面的代码使用了管道命令(|)。管道命令的作用,是将左侧命令(cat /etc/passwd)的标准输出转换为标准输入,提供给右侧命令(grep root)作为参数。
因为grep命令可以接受标准输入作为参数,所以上面的代码等同于下面的代码。
$ grep root /etc/passwd
但是,大多数命令都不接受标准输入作为参数,只能直接在命令行输入参数,这导致无法用管道命令传递参数。举例来说,echo命令就不接受管道传参。
$ echo "hello world" | echo
上面的代码不会有输出。因为管道右侧的echo不接受管道传来的标准输入作为参数。
xargs命令的作用,是将标准输入转为命令行参数。
$ echo "hello world" | xargs echo hello world
上面的代码将管道左侧的标准输入,转为命令行参数hello world,传给第二个echo命令。
xargs命令的格式如下。
$ xargs [-options] [command]
真正执行的命令,紧跟在xargs后面,接受xargs传来的参数。
xargs的作用在于,大多数命令(比如rm、mkdir、ls)与管道一起使用时,都需要xargs将标准输入转为命令行参数。
$ echo "one two three" | xargs mkdir
上面的代码等同于mkdir one two three。如果不加xargs就会报错,提示mkdir缺少操作参数。
xargs后面的命令默认是echo。
$ xargs # 等同于 $ xargs echo
大多数时候,xargs命令都是跟管道一起使用的。但是,它也可以单独使用。
输入xargs按下回车以后,命令行就会等待用户输入,作为标准输入。你可以输入任意内容,然后按下Ctrl d,表示输入结束,这时echo命令就会把前面的输入打印出来。
$ xargs hello (Ctrl + d) hello
再看一个例子。
$ xargs find -name "*.txt" ./foo.txt ./hello.txt
上面的例子输入xargs find -name以后,命令行会等待用户输入所要搜索的文件。用户输入"*.txt",表示搜索当前目录下的所有 TXT 文件,然后按下Ctrl d,表示输入结束。这时就相当执行find -name *.txt。
默认情况下,xargs将换行符和空格作为分隔符,把标准输入分解成一个个命令行参数。
$ echo "one two three" | xargs mkdir
上面代码中,mkdir会新建三个子目录,因为xargs将one two three分解成三个命令行参数,执行mkdir one two three。
-d参数可以更改分隔符。
$ echo -e "a\tb\tc" | xargs -d "\t" echo a b c
上面的命令指定制表符\t作为分隔符,所以a\tb\tc就转换成了三个命令行参数。echo命令的-e参数表示解释转义字符。
使用xargs命令以后,由于存在转换参数过程,有时需要确认一下到底执行的是什么命令。
-p参数打印出要执行的命令,询问用户是否要执行。
$ echo 'one two three' | xargs -p touch touch one two three ?...
上面的命令执行以后,会打印出最终要执行的命令,让用户确认。用户输入y以后(大小写皆可),才会真正执行。
-t参数则是打印出最终要执行的命令,然后直接执行,不需要用户确认。
$ echo 'one two three' | xargs -t rm rm one two three
由于xargs默认将空格作为分隔符,所以不太适合处理文件名,因为文件名可能包含空格。
find命令有一个特别的参数-print0,指定输出的文件列表以null分隔。然后,xargs命令的-0参数表示用null当作分隔符。
$ find /path -type f -print0 | xargs -0 rm
上面命令删除/path路径下的所有文件。由于分隔符是null,所以处理包含空格的文件名,也不会报错。
还有一个原因,使得xargs特别适合find命令。有些命令(比如rm)一旦参数过多会报错"参数列表过长",而无法执行,改用xargs就没有这个问题,因为它对每个参数执行一次命令。
$ find . -name "*.txt" | xargs grep "abc"
上面命令找出所有 TXT 文件以后,对每个文件搜索一次是否包含字符串abc。
如果标准输入包含多行,-L参数指定多少行作为一个命令行参数。
$ xargs find -name "*.txt" "*.md" find: paths must precede expression: `*.md'
上面命令同时将"*.txt"和*.md两行作为命令行参数,传给find命令导致报错。
使用-L参数,指定每行作为一个命令行参数,就不会报错。
$ xargs -L 1 find -name "*.txt" ./foo.txt ./hello.txt "*.md" ./README.md
上面命令指定了每一行(-L 1)作为命令行参数,分别运行一次命令(find -name)。
下面是另一个例子。
$ echo -e "a\nb\nc" | xargs -L 1 echo a b c
上面代码指定每行运行一次echo命令,所以echo命令执行了三次,输出了三行。
-L参数虽然解决了多行的问题,但是有时用户会在同一行输入多项。
$ xargs find -name "*.txt" "*.md" find: paths must precede expression: `*.md'
上面的命令将同一行的两项作为命令行参数,导致报错。
-n参数指定每次将多少项,作为命令行参数。
$ xargs -n 1 find -name
上面命令指定将每一项(-n 1)标准输入作为命令行参数,分别执行一次命令(find -name)。
下面是另一个例子。
$ echo {0..9} | xargs -n 2 echo 0 1 2 3 4 5 6 7 8 9
上面命令指定,每两个参数运行一次echo命令。所以,10个阿拉伯数字运行了五次echo命令,输出了五行。
如果xargs要将命令行参数传给多个命令,可以使用-I参数。
-I指定每一项命令行参数的替代字符串。
$ cat foo.txt one two three $ cat foo.txt | xargs -I file sh -c 'echo file; mkdir file' one two three $ ls one two three
上面代码中,foo.txt是一个三行的文本文件。我们希望对每一项命令行参数,执行两个命令(echo和mkdir),使用-I file表示file是命令行参数的替代字符串。执行命令时,具体的参数会替代掉echo file; mkdir file里面的两个file。
xargs默认只用一个进程执行命令。如果命令要执行多次,必须等上一次执行完,才能执行下一次。
--max-procs参数指定同时用多少个进程并行执行命令。--max-procs 2表示同时最多使用两个进程,--max-procs 0表示不限制进程数。
$ docker ps -q | xargs -n 1 --max-procs 0 docker kill
上面命令表示,同时关闭尽可能多的 Docker 容器,这样运行速度会快很多。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!