为什么需要xargs
管道实现的是将前面的stdout作为后面的stdin,但是有些命令不接受管道的传递方式,最常见的就是ls命令。有些时候命令希望管道传递的是参数,但是直接用管道有时无法传递到命令的参数位,这时候需要xargs,xargs实现的是将管道传输过来的stdin进行处理然后传递到命令的参数位上。也就是说xargs完成了两个行为:处理管道传输过来的stdin;将处理后的传递到正确的位置上。
可以试试运行下面的几条命令,应该能很好理解xargs的作用了:
[root@node2 scprits]# echo "/etc/inittab" | cat /etc/inittab [root@node2 scprits]# echo "/etc/inittab" | xargs cat # inittab is no longer used when using systemd. # # ADDING CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM. # # Ctrl-Alt-Delete is handled by /usr/lib/systemd/system/ctrl-alt-del.target # # systemd uses 'targets' instead of runlevels. By default, there are two main targets: # # multi-user.target: analogous to runlevel 3 # graphical.target: analogous to runlevel 5 # # To view current default target, run: # systemctl get-default # # To set a default target, run: # systemctl set-default TARGET.target #
xargs的作用不仅仅限于简单的stdin传递到命令的参数位,它还可以将stdin或者文件stdin分割成批,每个批中有很多分割片段,然后将这些片段按批交给xargs后面的命令进行处理。
通俗的讲就是原来只能一个一个传递,分批可以实现10个10个传递,每传递一次,xargs后面的命令处理这10个中的每一个,处理完了处理下一个传递过来的批,如下图。
但是应该注意的是,尽管实现了分批处理,但是默认情况下并没有提高任何效率,因为分批传递之后还是一次执行一个。而且有时候分批传递后是作为一个参数的整体,并不会将分批中的信息分段执行。这样看来,实现分批传递的目的仅仅是为了解决一些问题。但事实上,xargs提供了"-P"选项,用于指定并行执行的数量(默认只有一个处理进程,不会提升效率,可以指定为N个子进程,或者指定为0表示尽可能多地利用CPU),这样就能让分批操作更好地利用多核cpu,从而提升效率。例如上面分成了两批,指定"-P 2"可以并发执行这两个批,而非执行完第一批再执行第二批。关于并行处理的详细内容,见后文:高速并行处理之:xargs -P。
剩下的就是处理xargs的细节问题了,比如如何分割(xargs、xargs -d、xargs -0),分割后如何划批(xargs -n、xargs -L),参数如何传递(xargs -i)。另外xargs还提供询问交互式处理(-p选项)和预先打印一遍命令的执行情况(-t选项),传递终止符(-E选项)等。
其实这里已经暗示了xargs处理的优先级或顺序了:先分割,再分批,然后传递到参数位。
分割有三种方法:独立的xargs、xargs -d和xargs -0。后两者可以配合起来使用,之所以不能配合独立的xargs使用,答案是显然的,指定了-d或-0选项意味着它不再是独立的。
分批方法从逻辑上说是两种:-n选项和-L选项。但我觉得还应该包含传递阶段的选项-i。假如-i不是分批选项,则它将接收分批的结果。然而事实并非如此,当-i选项指定在-n和-L选项之后,会覆盖-n或-L。后文中我将其当成分批选项来介绍和说明。
当然上述只是一个概括,更具体的还要看具体的选项介绍,而且很可能一个xargs中用不到这么多选项,但是理解这个很重要,否则在分割分批和传递上很容易出现疑惑。
文本意义上的符号和标记意义上的符号
在解释xargs和它的各种选项之前,我想先介绍一个贯穿xargs命令的符号分类:文本意义上的空格、制表符、反斜线、引号和非文本意义上的符号。我觉得理解它们是理解xargs分割和分批原理的关键。
文本意义上的空格、制表符、反斜线、引号:未经处理就已经存在的符号,例如文本的内容中出现这些符号以及在文件名上出现了这些符号都是文本意义上的。与之相对的是非文本意义的符号,由于在网上没找到类似的文章和解释,所以我个人称之为标记意义上的符号:处理后出现的符号,例如ls命令的结果中每个文件之间的制表符,它原本是不存在的,只是ls命令处理后的显示方式。还包括每个命令结果的最后的换行符,文件内容的最后一行结尾的换行符。
如下图,属于标记意义上的符号都用红色圆圈标记出来了。
其实它们的关系有点类似于字面意义的符号和特殊符号之间的关系,就像有时候特殊符号需要进行转义才能表示为普通符号。
因为翻了百度、谷歌和一些书都没说这些方面的分类。但文本和非文本的符号在xargs分割的时候确实是区别对待的,所以我觉得有必要给个称呼好引用并说明它们,也就是说以上称呼完全是我个人的称呼。
分割行为之:xargs
[root@node2 scprits]# cd /tmp [root@node2 tmp]# rm -fr * [root@node2 tmp]# ls [root@node2 tmp]# mkdir a b c d test logdir shdir [root@node2 tmp]# touch "one space.log" [root@node2 tmp]# touch logdir/{1..10}.log [root@node2 tmp]# touch shdir/{1..5}.sh [root@node2 tmp]# echo "the second sh the second line" > shdir/2.sh [root@node2 tmp]# cat <<eof>shdir/1.sh > > the first sh > the second line > eof
对于xargs,它将接收到的stdout处理后传递到xargs后面的命令参数位,不写命令时默认的命令是echo。
[root@node2 tmp]# cat shdir/1.sh | xargs the first sh the second line [root@node2 tmp]# cat shdir/1.sh | xargs echo the first sh the second line
将分行处理掉不是echo实现的,而是管道传递过来的stdin经过xargs处理后的:将所有空格、制表符和分行符都替换为空格并压缩到一行上显示,这一整行将作为一个整体,这个整体的所有空格属性继承xargs处理前的符号属性,即原来是文本意义的或标记意义的在替换为空格后符号属性不变。这个整体可能直接交给命令或者作为stdout通过管道传递给管道右边的命令,这时结果将作为整体传递,也可能被xargs同时指定的分批选项分批处理。
如果想要保存制表符、空格等特殊符号,需要将它们用单引号或双引号包围起来,但是单双引号(和反斜线)都会被xargs去掉。
另外经过我的测试,单引号和双引号的存在让处理变的很不受控制,经常会影响正常的分割和处理。
如果不指定分批选项,xargs的一整行结果将作为一个整体输出,而不是分隔开的。也许看处理的结果感觉是分开处理的,例如下面的第一个命令,但是这是因为ls允许接受多个空格分开的参数,执行第二个命令,可以证明它确实是将整行作为整体传输给命令的。
[root@node2 tmp]# find /tmp -maxdepth 1 | xargs ls /tmp/one_space.log /tmp: a b c d logdir one_space.log shdir test /tmp/a: /tmp/b: /tmp/c: /tmp/d: /tmp/.font-unix: /tmp/.ICE-unix: /tmp/logdir: 10.log 1.log 2.log 3.log 4.log 5.log 6.log 7.log 8.log 9.log /tmp/shdir: 1.sh 2.sh 3.sh 4.sh 5.sh /tmp/test: /tmp/.Test-unix: /tmp/.X11-unix: /tmp/.XIM-unix:
[root@node2 tmp]# find /tmp -maxdepth 1 | xargs -p ls ls /tmp /tmp/.ICE-unix /tmp/.Test-unix /tmp/.font-unix /tmp/.XIM-unix /tmp/.X11-unix /tmp/a /tmp/b /tmp/c /tmp/d /tmp/test /tmp/logdir /tmp/shdir /tmp/one_space.log ?...
如果对独立的xargs指定分批选项,则有两种分批可能:指定-n时按空格分段,然后划批,不管是文本意义的空格还是标记意义的空格,只要是空格都是-n的操作对象;指定-L或者-i时按段划批,文本意义的符号不被处理。
[root@node2 tmp]# ls a b c d logdir one_space.log shdir test [root@node2 tmp]# ls | xargs -n 2 -n是按空格分割的 a b c d logdir one_space.log shdir test
[root@node2 tmp]# ls | xargs -L 2 a b c d logdir one_space.log shdir test 文件名中的空格没有分割这个段
[root@node2 tmp]# ls | xargs -i -p echo {} echo a ?... echo b ?... echo c ?... echo d ?... echo logdir ?... echo one_space.log ?... echo shdir ?... echo test ?...
使用xargs -p或xargs -t观察命令的执行过程
使用-p选项是交互询问式的,只有每次询问的时候输入y(或yes)才会执行,直接按enter键是不会执行的。
使用-t选项是在每次执行xargs后面的命令都会先在stderr上打印一遍命令的执行过程然后才正式执行。
使用-p或-t选项就可以根据xargs后命令的执行顺序进行推测,xargs是如何分段、分批以及如何传递的,这通过它们有助于理解xargs的各种选项。
[root@node2 tmp]# ls | xargs -n 2 -t echo a b 先打印一次命令,表示这一次只echo两个参数:a和b a b echo c d 表示这次只打印c和d
c d
c d echo logdir one_space.log logdir one_space.log echo shdir test shdir test
[root@node2 tmp]# ls | xargs -n 2 -p echo a b ?...y a b echo c d ?...y c d echo logdir one_space.log ?...y logdir one_space.log echo shdir test ?...y shdir test
从上面的-t和-p的结果上都可以知道每次传递两个参数。
分割行为之:xargs -d
xargs -d有如下行为:
? xargs -d可以指定分段符,可以是单个符号、字母或数字。如指定字母o为分隔符:xargs -d"o"。
? xargs -d是分割阶段的选项,所以它优先于分批选项(-n、-L、-i)。
? xargs -d不是先xargs再-d处理的,它是区别于独立的xargs的另一个分割选项。
xargs -d整体执行有几个阶段:
? 替换:将接收stdin的所有的标记意义的符号替换为 ,替换完成后所有的符号(空格、制表符、分行符)变成字面意义上的普通符号,即文本意义的符号。
? 分段:根据-d指定的分隔符进行分段并用空格分开每段,由于分段前所有符号都是普通字面意义上的符号,所以有的分段中可能包含了空格、制表符、分行符。也就是说除了-d导致的分段空格,其余所有的符号都是分段中的一部分。
? 输出:最后根据指定的分批选项来输出。这里需要注意,分段前后有特殊符号时会完全按照符号输出。
从上面的阶段得出以下两结论:
(1)xargs -d会忽略文本意义上的符号。对于文本意义上的空格、制表符、分行符,除非是-d指定的符号,否则它们从来不会被处理,它们一直都是每个分段里的一部分;
(2)由于第一阶段标记意义的符号会替换为分行符号,所以传入的stdin的每个标记意义符号位都在最终的xargs -d结果上分行了,但是它们已经是分段中的普通符号了,除非它们是-d指定的符号。
例如对ls的结果指定"o"为分隔符。
[root@node2 tmp]# ls a b c d logdir one_space.log shdir test [root@node2 tmp]# ls | xargs -d"o" a b c d l gdir ne_space.l g shdir test #指定字母"o"为分隔符
如果使用xargs -d时不指定分批选项,则整个结果将作为整体输出。
[root@node2 tmp]# ls | xargs -d"o" -p echo a b c d l gdir ne_space.l g shdir test ?...y a b c d l gdir ne_space.l g shdir test
如果指定了分批选项,则按照-d指定的分隔符分段后的段分批,这时使用-n、-L或-i的结果是一样的。例如使用-n选项来观察是如何分批的。
[root@node2 tmp]# ls | xargs -d"o" -n 2 -t echo a b c d l gdir 每两段是一个批。 a b c d l gdir # 注意这里有个空行。是因为段的分隔符处于下一段的行开头,它的前面有个 符号会按符号输出。 echo ne_space.l g shdir test ne_space.l g shdir test
[root@node2 tmp]# [root@xuexi tmp]# ls | xargs -d"o" -n 2 -bash: [root@xuexi: 未找到命令 [root@node2 tmp]# ls | xargs -d"o" -n 2 a b c d l gdir ne_space.l g shdir test
再看看-n 1的输出。
[root@node2 tmp]# ls | xargs -d"o" -n 1 a b c d l gdir ne_space.l g shdir test
分割行为之:xargs -0
xargs -0的行为和xargs -d基本是一样的,只是-d是指定分隔符,-0是指定固定的 作为分隔符。其实xargs -0就是特殊的xargs -d的一种,它等价于xargs -d" "。
xargs -0行为如下:
? xargs -0是分割阶段的选项,所以它优先于分批选项(-n、-L、-i)。
? xargs -0不是先xargs再-0处理的,它是区别于独立的xargs的另一个分割选项。
? xargs -0可以处理接收的stdin中的null字符(