xargs的原理剖析及用法详解
Posted 骏马金龙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了xargs的原理剖析及用法详解相关的知识,希望对你有一定的参考价值。
bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html
学习这个xargs花了很长时间,在网上翻了很久也查了很多书关于xargs的介绍,都只是简单的介绍了它的几个用法,却没有介绍它工作的原理,man也只有简单的介绍,并没有说各个选项之间配合时的情况。所以我只能自己探索了,探索的路上确实充满了荆棘,不断的总结却不断的被实验推翻,每当以为自己得出了结论,却往往发现不够完善,所以我自己也是边测试边删改完成这篇学习记录,但是不得不说这过程充满了乐趣。
个人感觉xargs的基本用法很简单,它的选项实现的功能也很简单,但是多个选项配合时问题就变得很难很难,经常发现结果和预期不同。
这一整篇是我个人的总结和记录,由于完全是自己一个人在短短几天内探索出来的,所以难免会有所遗漏、错误,甚至可能是误人的结论,万望见谅。如果有朋友发现了其中的问题盼请不吝指出,本人将感激不尽。
1.1 为什么需要xargs
管道实现的是将前面的stdout作为后面的stdin,但是有些命令不接受管道的传递方式,最常见的就是ls命令。有些时候命令希望管道传递的是参数,但是直接用管道有时无法传递到命令的参数位,这时候需要xargs,xargs实现的是将管道传输过来的stdin进行处理然后传递到命令的参数位上。也就是说xargs完成了两个行为:处理管道传输过来的stdin;将处理后的传递到正确的位置上。
可以试试运行下面的几条命令,应该能很好理解xargs的作用了:
[root@xuexi tmp]# echo "/etc/inittab" | cat # 直接将标准输入的内容传递给cat [root@xuexi tmp]# echo "/etc/inittab" | xargs cat # 将标准输入的内容经过xargs处理后传递给cat
[root@xuexi tmp]# find /etc -maxdepth 1 -name "*.conf" -print0 | xargs -0 -i grep "hostname" -l {} # 将搜索的文件传递给grep的参数位进行搜索,若不使用xargs,则grep将报错
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中用不到这么多选项,但是理解这个很重要,否则在分割分批和传递上很容易出现疑惑。
1.2 文本意义上的符号和标记意义上的符号
在解释xargs和它的各种选项之前,我想先介绍一个贯穿xargs命令的符号分类:文本意义上的空格、制表符、反斜线、引号和非文本意义上的符号。我觉得理解它们是理解xargs分割和分批原理的关键。
文本意义上的空格、制表符、反斜线、引号:未经处理就已经存在的符号,例如文本的内容中出现这些符号以及在文件名上出现了这些符号都是文本意义上的。与之相对的是非文本意义的符号,由于在网上没找到类似的文章和解释,所以我个人称之为标记意义上的符号:处理后出现的符号,例如ls命令的结果中每个文件之间的制表符,它原本是不存在的,只是ls命令处理后的显示方式。还包括每个命令结果的最后的换行符,文件内容的最后一行结尾的换行符。
如下图,属于标记意义上的符号都用红色圆圈标记出来了。
其实它们的关系有点类似于字面意义的符号和特殊符号之间的关系,就像有时候特殊符号需要进行转义才能表示为普通符号。
因为翻了百度、谷歌和一些书都没说这些方面的分类。但文本和非文本的符号在xargs分割的时候确实是区别对待的,所以我觉得有必要给个称呼好引用并说明它们,也就是说以上称呼完全是我个人的称呼。
1.3 分割行为之:xargs
[root@xuexi tmp]# cd /tmp [root@xuexi tmp]# rm -fr * [root@xuexi tmp]# mkdir a b c d test logdir shdir [root@xuexi tmp]# touch "one space.log" [root@xuexi tmp]# touch logdir/{1..10}.log [root@xuexi tmp]# touch shdir/{1..5}.sh [root@xuexi tmp]# echo "the second sh the second line" > shdir/2.sh [root@xuexi tmp]# cat <<eof>shdir/1.sh > the first sh > the second line > eof
对于xargs,它将接收到的stdout处理后传递到xargs后面的命令参数位,不写命令时默认的命令是echo。
[root@xuexi tmp]# cat shdir/1.sh | xargs
the first sh the second line
[root@xuexi tmp]# cat shdir/1.sh | xargs echo
the first sh the second line
将分行处理掉不是echo实现的,而是管道传递过来的stdin经过xargs处理后的:将所有空格、制表符和分行符都替换为空格并压缩到一行上显示,这一整行将作为一个整体,这个整体的所有空格属性继承xargs处理前的符号属性,即原来是文本意义的或标记意义的在替换为空格后符号属性不变。这个整体可能直接交给命令或者作为stdout通过管道传递给管道右边的命令,这时结果将作为整体传递,也可能被xargs同时指定的分批选项分批处理。
如果想要保存制表符、空格等特殊符号,需要将它们用单引号或双引号包围起来,但是单双引号(和反斜线)都会被xargs去掉。
另外经过我的测试,单引号和双引号的存在让处理变的很不受控制,经常会影响正常的分割和处理。
如果不指定分批选项,xargs的一整行结果将作为一个整体输出,而不是分隔开的。也许看处理的结果感觉是分开处理的,例如下面的第一个命令,但是这是因为ls允许接受多个空格分开的参数,执行第二个命令,可以证明它确实是将整行作为整体传输给命令的。
[root@xuexi tmp]# find /tmp -maxdepth 1 | xargs ls
/tmp/sh.txt
/tmp:
a b c d logdir shdir sh.txt test
/tmp/a:
/tmp/b:
/tmp/c:
/tmp/d:
/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 hell sh.txt
/tmp/test:
[root@xuexi tmp]# find /tmp -maxdepth 1 | xargs -p ls # -p选项后面有解释
ls /tmp /tmp/x.txt /tmp/logdir /tmp/b /tmp/test /tmp/d /tmp/vmware-root /tmp/sh.txt /tmp/c /tmp/shdir /tmp/a /tmp/one space.log /tmp/.ICE-unix ?...
如果对独立的xargs指定分批选项,则有两种分批可能:指定-n时按空格分段,然后划批,不管是文本意义的空格还是标记意义的空格,只要是空格都是-n的操作对象;指定-L或者-i时按段划批,文本意义的符号不被处理。
[root@xuexi tmp]# ls #one space.log是一个文件的文件名,只是包含了空格
a b c d logdir one space.log shdir sh.txt test vmware-root x.txt
[root@xuexi tmp]# ls | xargs -n 2
a b
c d
logdir one # one和space.log分割开了,说明-n是按空格分割的
space.log shdir
sh.txt test
vmware-root x.txt
[root@xuexi tmp]# ls | xargs -L 2
a b
c d
logdir one space.log # one space.log作为一个分段,文件名中的空格没有分割这个段
shdir sh.txt
test vmware-root
x.txt
[root@xuexi tmp]# ls | xargs -i -p echo {}
echo a ?...
echo b ?...
echo c ?...
echo d ?...
echo logdir ?...
echo one space.log ?... # one space.log也没有被文件名中的空格分割
echo shdir ?...
echo sh.txt ?...
echo test ?...
echo vmware-root ?...
echo x.txt ?...
1.4 使用xargs -p或xargs -t观察命令的执行过程
使用-p选项是交互询问式的,只有每次询问的时候输入y(或yes)才会执行,直接按enter键是不会执行的。
使用-t选项是在每次执行xargs后面的命令都会先在stderr上打印一遍命令的执行过程然后才正式执行。
使用-p或-t选项就可以根据xargs后命令的执行顺序进行推测,xargs是如何分段、分批以及如何传递的,这通过它们有助于理解xargs的各种选项。
[root@xuexi tmp]# ls | xargs -n 2 -t
/bin/echo a b # 先打印一次命令,表示这一次只echo两个参数:a和b
a b
/bin/echo c d # 表示这次只打印c和d
c d
/bin/echo logdir one
logdir one
/bin/echo space.log shdir
space.log shdir
/bin/echo sh.txt test
sh.txt test
/bin/echo vmware-root
vmware-root
[root@xuexi tmp]# ls | xargs -n 2 -p
/bin/echo a b ?...y # 询问是否echo a b
/bin/echo c d ?...a b
y # 询问是否echo c d,后面的...a b指示了echo c d是在前一个结果的基础上接着执行的
/bin/echo logdir one ?...c d
y
/bin/echo space.log shdir ?...logdir one
y
/bin/echo sh.txt test ?...space.log shdir
y
/bin/echo vmware-root ?...sh.txt test
y
vmware-root
从上面的-t和-p的结果上都可以知道每次传递两个参数。
1.5 分割行为之:xargs -d
xargs -d有如下行为:
? xargs -d可以指定分段符,可以是单个符号、字母或数字。如指定字母o为分隔符:xargs -d"o"。
? xargs -d是分割阶段的选项,所以它优先于分批选项(-n、-L、-i)。
? xargs -d不是先xargs再-d处理的,它是区别于独立的xargs的另一个分割选项。
xargs -d整体执行有几个阶段:
? 替换:将接收stdin的所有的标记意义的符号替换为\\n,替换完成后所有的符号(空格、制表符、分行符)变成字面意义上的普通符号,即文本意义的符号。
? 分段:根据-d指定的分隔符进行分段并用空格分开每段,由于分段前所有符号都是普通字面意义上的符号,所以有的分段中可能包含了空格、制表符、分行符。也就是说除了-d导致的分段空格,其余所有的符号都是分段中的一部分。
? 输出:最后根据指定的分批选项来输出。这里需要注意,分段前后有特殊符号时会完全按照符号输出。
从上面的阶段得出以下两结论:
(1)xargs -d会忽略文本意义上的符号。对于文本意义上的空格、制表符、分行符,除非是-d指定的符号,否则它们从来不会被处理,它们一直都是每个分段里的一部分;
(2)由于第一阶段标记意义的符号会替换为分行符号,所以传入的stdin的每个标记意义符号位都在最终的xargs -d结果上分行了,但是它们已经是分段中的普通符号了,除非它们是-d指定的符号。
例如对ls的结果指定"o"为分隔符。
[root@xuexi tmp]# ls
a b c d logdir one space.log shdir sh.txt test vmware-root
[root@xuexi tmp]# ls | xargs -d"o" #指定字母"o"为分隔符
分段结果如图所示,图中每个封闭体都是一个分段,这些分段里可能包含了分行,可能包含了空格。
如果使用xargs -d时不指定分批选项,则整个结果将作为整体输出。
[root@xuexi tmp]# ls | xargs -d"o" -p
/bin/echo a
b
c
d
l gdir
ne space.l g
shdir
sh.txt
test
vmware-r t
x.txt
?...
如果指定了分批选项,则按照-d指定的分隔符分段后的段分批,这时使用-n、-L或-i的结果是一样的。例如使用-n选项来观察是如何分批的。
[root@xuexi tmp]# ls | xargs -d"o" -n 2 -t
/bin/echo a
b
c
d
l gdir # 每两段是一个批。
a
b
c
d
l gdir
# 注意这里有个空行。是因为段的分隔符处于下一段的行开头,它的前面有个\\n符号会按符号输出。
/bin/echo ne space.l g
shdir
sh.txt
test
vmware-r # 打印中间两段
ne space.l g
shdir
sh.txt
test
vmware-r
/bin/echo t # 打印最后一段,
t # 注意t前面有空格,因为是两个分隔符o连在一起分割的,所以前面有个空格需要输出。
下面是最终显示结果。
[root@xuexi tmp]# ls | xargs -d"o" -n 2
a
b
c
d
l gdir
ne space.l g
find -print0和xargs -0原理及用法