xargs的原理剖析及用法详解(3)

或者用下面的方法来证明。如果-n选项优先级高,则one space.log将被分开,但是和-L使用的时候并没有被分开。

[root@linuxidc tmp]# ls | xargs -n 3

a b c

d logdir one

space.log shdir sh.txt

test vmware-root x.txt

[root@linuxidc tmp]# ls | xargs -n 3 -L 3 # -L分批覆盖了-n分批

a b c

d logdir one space.log

shdir sh.txt test

vmware-root x.txt

根据上面的证明,其实也就给出了我认为-i选项是分批选项的理由。因为它覆盖了-n和-L。其实如果说-i包含分批并传递这两个作用更严格一点

1.7.5 分批选项的一个典型应用

分批选项有时特别有用,例如脚本规定每次只能传输三个参数。有时候rm -rf的文件数量特别多的时候会提示参数列表太长而导致失败,这时就可以分批来按批删除,不仅rm -rf,其他很多本身就可以实现批量操作的命令都有可能出现这种参数列表过长的错误,如touch {1..10000000}也会提示错误。

假设目前在/tmp/longshuai/下有29W个.log文件,如果直接删除将会提示参数列表过长。

[root@linuxidc tmp]# rm -fr /tmp/longshuai/*.log

-bash: /bin/rm: Argument list too long

这时如果使用xargs就可以分批丢给rm -fr处理了。下面一批10000个,删除29批。

[root@linuxidc tmp]# cd /tmp/longshuai/ && ls | xargs -n 10000 rm -rf

如果不使用分批直接交给rm -rf也是一样可以执行成功的。如果想知道为什么可以请看后文xargs -s。

[root@linuxidc tmp]# cd /tmp/longshuai/ && ls | xargs rm -rf

这里说下如何统计某个目录下的文件数量?ll后使用"-"开头来过滤出文件,然后使用wc统计行数。

[root@linuxidc tmp]# ll /tmp/longshuai/ | grep "^-" | wc -l

1.8 终止行为之:xargs -E

指定终止符号,搜索到了指定的终止符就完全退出传递,命令也就到此结束。

-e选项也是,但是官方建议使用-E替代-e,因为-E是POSIX标准兼容的,而-e不是。

-E会将结果空格、制表符、分行符替换为空格并压缩到一行上显示。

据我测试,-E似乎只能和独立的xargs使用,和-0、-d配合使用时都会失效。那么稍后我就只测试和独立的xargs配合使用的情况了。

-E优先于-n、-L和-i执行。如果是分批选项先执行,则下面的第二个结果将压缩在一行上。

指定的终止符必须是完整的,例如想在遇到“xyz.txt”的符号终止时,只能指定完整的xyz.txt符号,不能指定.txt或者txt这样的符号。如何判断指定的终止符号是否完整,就-E与独立的xargs配合的情况而言分两种情况:如果没指定分批选项或者指定的分批选项是-n或者-L时,以空格为分割符,两个空格之间的段都是完整的;如果指定的分批选项是-i,则以段为分割符。

例如,下面的示例。观察实验结果中的one space.log分割的情况。

[root@linuxidc tmp]# ls

a  b  c  d  logdir  one space.log  shdir  sh.txt  test  vmware-root  x.txt

[root@linuxidc tmp]# ls | xargs -E one #不指定分批选项

a b c d logdir

[root@linuxidc tmp]# ls | xargs -n 2 -E one #指定-n,one后面的所有的都终止传递

a b

c d

logdir

[root@linuxidc tmp]# ls | xargs -L 2 -E"one" #同-n 选项

a b

c d

logdir

[root@linuxidc tmp]# ls | xargs -i -E"one space.log" echo {} #和-i配合使用时指定完整的段才可以

a

b

c

d

logdir

[root@linuxidc tmp]# ls | xargs -i -E"one" -p echo {} #非完整段终止失效

echo a ?...

echo b ?...

echo c ?...

echo d ?...

echo logdir ?...

echo one space.log ?...

echo shdir ?...

echo sh.txt ?...

echo test ?...

echo vmware-root ?...

echo x.txt ?...

1.9 xargs的处理总结

总结只有一张表。算是用来复习前面所述。

分割行为

 

特殊符号处理方式

 

分段方法

 

配合分批选项

 

分批方法

 

xargs

 

空格、制表符、分行符替换为空格,引号和反斜线删除。处理完后只有空格。如果空格、制表符和分行符使用引号包围则可以保留

 

结果继承处理前的符号性质(文本符号还是标记意义符号)。

 

-n

 

以分段结果中的每个空格分段,进而分批。不管是文本还是标记意义的空格,只要是空格

 

-L、-i

 

以标记意义上的空格分段,进而分批

 

不指定

 

结果作为整体输出

 

xargs -d

 

xargs -d  不处理文本意义上的符号,所有标记意义上的符号替换为换行符\n,将-d指定的分割符替换为标记意义上的空格。

结果中除了最后的空行和-d指定的分割符位的分段空格,其余全是文本意义上的符号

 

按照-d指定的符号进行分段,每个段中可能包含文本意义上的空格、制表符、甚至是分行符。

 

-n、-L、-i

 

以标记意义上的符号(即最后的空行和-d指定分隔符位的空格)分段,进而分批。分段结果中保留所有段中的符号,包括制表符和分行符。

 

不指定

 

结果作为整体输出

 

xargs -0

 

不处理文本意义上的符号,将非\0的标记意义上的符号替换为\n,将\0替换为空格。

结果中除了最后空行和\0位的空格,其余都是文本意义上的符号

 

以替换\0位的空格分段,每个段中可能包含文本意义上的空格、制表符、甚至是分行符。

如果没检测到\0,则只有一个不可分割的段。

 

-n、-L、-i

 

检测到\0时,以标记意义上的符号(即最后的空行和\0位的空格)分段,进而分批。分段结果中保留所有段中的符号,包括制表符和分行符。

 

未检测到\0时,整个结果作为不可分割整体,使用分批选项是无意义的

 

不指定

 

结果作为整体输出

 
1.10 xargs与find的结合

xargs和find同属于一个rpm包findutils,xargs原本就是为find而开发的,它们之间的配合应当是天衣无缝的。

一般情况下它们随意结合都无所谓,按正常方式进行即可。但是当删除文件时,特别需要将文件名含有空白字符的文件纳入考虑。

[root@linuxidc tmp]# touch one;touch space.log [root@linuxidc tmp]# ls

a  b  c  d  logdir  one  one space.log  shdir  sh.txt  space.log  test  vmware-root

现在假设通过find搜索到了one space.log。

[root@linuxidc tmp]# find -name "* *.log"

./one space.log

如果直接交给xargs rm -rf,由于xargs处理后不指定分批选项时以空格分段,所以改名了的行为将是rm -rf ./one space.log,这表示要删除的是当前目录下的one和当前目录下的space.log,而不是one space.log。

有多种方法可以解决这个问题。思路是让找到的“one space.log”成为一个段,而不是两个段。我给出了常见的两种。

方法一:通过常用的find的-print0选项使用\0来分隔而不是\n分隔,再通过xargs -0来配对保证one space.log的整体性。因为-print0后one space.log的前后各有一个\0,但是文件名中间没有。

[root@linuxidc tmp]# find -name "* *.log" -print0 | xargs -0 rm -rf

当然,能使用-0肯定也能使用-d了。

[root@linuxidc tmp]# find -name "* *.log" -print0 | xargs -d "x" rm -rf #随意指定非文件名中的字符都行,不一定非要\0

方法二:不在find上处理,在xargs上处理,只要通过配合-i选项,就能宣告它的整体性。

[root@linuxidc tmp]# find -name "* *.log" | xargs -i rm -rf "{}"

相较而言,方法一使用的更广泛更为人所知,但是方法二更具有通用性,对于非find如ls命令也可以进行处理。

还可以使用tr将find的换行符换成其他符号再xargs分割配对也行。

除了find -print0可以输出\0字符,Linux中还有其他几个命令配合参数也可以实现:locate -0,grep -zgrep -Z,sort -z等

1.11 xargs -s之为什么ls | xargs rm -rf能执行成功?

使用下面的示例配合图来解释。

[root@linuxidc tmp]# cd logdir [root@linuxidc logdir]# touch {1..1000000}

-bash: /bin/touch: Argument list too long

[root@linuxidc logdir]# echo {1..1000000} | xargs touch #执行的时候记得使用-p选项,否则慢慢等吧。

问题一:正常创建批量文件touch {1..1000000}是无法执行成功的,会提示参数列表过长。但是上面的最后一个命令为什么能执行成功?

问题二:xargs处理后如果不指定-n选项,那么它是整体传递的,如果这个整体非常非常大,如上面的100W个参数,按理说touch也是无法成功的。为什么成功了?

xargs有一个默认的选项-s,它指定每次传递的最大字节数,如果不显式指定-s,系统默认是128KB。也就是说如果一次传递的参数很多很大,那么将由系统自动分割为每128KB传递一次。这就是上面的命令能执行成功的原因。

上面的100W个参数,以差不多每个参数5个数字位加一个分段位空格共6个字节计算,128K有128*1024/6=21845个数字,这和我使用-p测试的询问位置是接近的,如下图,由于前10000个数字少于5个字节,所以比21845多一点。第二次停止的位置是45539,45539-23695=21844,这次传递的全是5个字节的,这和计算的结果几乎完全相同。

同理“ls | xargs rm -rf”也是一样的,如果参数列表非常大,则每次传递128K的参数给rm。

1.12 创建文件名包含分行符的文件

创建文件名包含空格的文件是一件很轻松的事情,但是想创建包含制表符、分行符甚至是其他特殊符号的文件呢?

因为xargs允许传递参数到命令的任意参数位,并且传递的参数还可以变换为包含各种形式的特殊符号,所以使用它可以轻松实现。例如创建包含分行符的文件。

[root@linuxidc tmp]# ls

a  b  c  d  logdir  one space.log  shdir  sh.txt  test  vmware-root

[root@linuxidc tmp]# ls | xargs -0

a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

在此基础上创建一个.sh文件,这个文件将奇形怪状,因为文件名竟然包含了分行符(Linux中文件名除了"/"和"\0"外所有字符都允许包含在内)

[root@linuxidc tmp]# ls | xargs -0 -i touch {}.sh [root@linuxidc tmp]# ls

a                                                              b  d      one space.log  sh.txt  vmware-root

a?b?c?d?logdir?one space.log?shdir?sh.txt?test?vmware-root?.sh  c  logdir  shdir          test

看上去只是有几个问号,但是使用?是无法定位它的。

[root@linuxidc tmp]# find -name "*[\?]*" #搜索没结果 或者 [root@linuxidc tmp]# rm -rf a #按两次tab键

a/

a^Jb^Jc^Jd^Jlogdir^Jone space.log^Jshdir^Jsh.txt^Jtest^Jvmware-root^J.sh

现在使用xargs就可以轻松显示它的文件名。

[root@linuxidc tmp]# ls | xargs -0

a

a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

.sh

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

不能直接使用xargs显示,因为它会压缩空白符号成空格。

[root@linuxidc tmp]# ls | xargs

a a b c d logdir one space.log shdir sh.txt test vmware-root .sh b c d logdir one space.log shdir sh.txt test vmware-root

删除它。

[root@linuxidc tmp]# rm -f a*.sh

如果想创建文件名只包含下面结果的abcd前四行的.sh文件呢?

[root@linuxidc tmp]# ls | xargs -0

a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

参考下面的。

[root@linuxidc tmp]# ls | xargs -n 1 -e"logdir" | xargs -0 -i touch {}.sh

这就需要理解前面介绍的xargs的分割和传递方法了。

也可以使用下面更简单容易理解的:

[root@linuxidc tmp]# ls | head -n 4 | xargs -0 -i touch {}.sh [root@linuxidc tmp]# echo -e "a\nb\nc\nd" | xargs -0 -i touch {}.log

那么以相同的方法创建文件名中包含制表符的文件就easy了。

[root@linuxidc tmp]# echo -e "a\tb\tc\td" | xargs -0 -i touch {}.log

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/13325.html