1. Linux xargs命令详解

管道实现的是将前面的stdout作为后面的stdin ,但是有些命令不接受管道的传递方式 , 最常见的就是ls命令。有些时候命令希望管道传递的是参数 , 但是直接用管道有时无法传递到命令的参数位 , 这时候需要xargs ,xargs实现的是将管道传输过来的stdin进行处理然后传递到命令的参数位上。也就是说xargs完成了两个行为 : 处理管道传输过来的stdin ; 将处理后的传递到正确的位置上。

# 直接将标准输入的内容传递给cat
[root@centos7 ~]# echo "/etc/inittab" | cat 
/etc/inittab

# 将标准输入的内容经过xargs处理后传递给cat
[root@centos7 ~]# 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
#

#将搜索的文件传递给grep的参数位进行搜索, 若不使用xargs, 则grep将报错
[root@centos7 ~]#  find /etc -maxdepth 1 -name "*.conf" -print0 | xargs -0 -i grep "hostname" -l {}
/etc/nsswitch.conf
/etc/sudo-ldap.conf

xargs选项说明

选项

说明

-n

指定每次执行命令时使用的参数个数。

-I

用输入项替换占位符(通常为 {})。

-p

交互式模式, 执行前提示用户确认。

-t

打印执行的命令。

-r

如果输入为空, 则不执行命令。

-d

指定输入的分隔符(默认是空格和换行)。

-0

null 字符作为分隔符

-a

从文件读取输入, 而不是标准输入。

-L

指定每次执行命令时使用的行数。

-s

设置命令行的最大长度。

--max-procs ,-P

并行执行命令, 指定最大进程数。

-i

xargs的每项名称 , 一般是一行一行赋值给 {} , 可以用 {} 代替。

xargs的作用不仅仅限于简单的stdin传递到命令的参数位 , 它还可以将stdin或者文件stdin分割成批, 每个批中有很多分割片段, 然后将这些片段按批交给xargs后面的命令进行处理。

通俗的讲就是原来只能一个一个传递 , 分批可以实现1010个传递 , 每传递一次, xargs后面的命令处理这10个中的每一个 , 处理完了处理下一个传递过来的批 , 如下图。

但是应该注意的是 , 尽管实现了分批处理 , 但是默认情况下并没有提高任何效率 , 因为分批传递之后还是一次执行一个。而且有时候分批传递后是作为一个参数的整体 , 并不会将分批中的信息分段执行。这样看来 ,实现分批传递的目的仅仅是为了解决一些问题。但事实上 , xargs提供了-P选项 , 用于指定并行执行的数量(默认只有一个处理进程 , 不会提升效率 , 可以指定为N个子进程 , 或者指定为0表示尽可能多地利用CPU) , 这样就能让分批操作更好地利用多核cpu ,从而提升效率。例如上面分成了两批 , 指定-P 2可以并发执行这两个批, 而非执行完第一批再执行第二批。

剩下的就是处理xargs的细节问题了 , 比如如何分割(xargsxargs -dxargs -0) , 分割后如何划批(xargs -nxargs -L) ,参数如何传递(xargs -i)。另外xargs还提供询问交互式处理(-p选项)和预先打印一遍命令的执行情况(-t选项) , 传递终止符(-E选项)等。

其实这里已经暗示了xargs处理的优先级或顺序了 : 先分割 , 再分批 , 然后传递到参数位。

分割有三种方法:独立的xargsxargs -dxargs -0。后两者可以配合起来使用 , 之所以不能配合独立的xargs使用 , 答案是显然的 , 指定了-d-0选项意味着它不再是独立的。

分批方法从逻辑上说是两种 : -n选项和-L选项。但我觉得还应该包含传递阶段的选项-i。假如-i不是分批选项 , 则它将接收分批的结果。然而事实并非如此 , 当-i选项指定在-n-L选项之后 , 会覆盖-n-L

当然上述只是一个概括, 更具体的还要看具体的选项介绍 , 而且很可能一个xargs中用不到这么多选项 , 但是理解这个很重要, 否则在分割分批和传递上很容易出现疑惑。

[root@centos7 ~]# cd /tmp
[root@centos7 tmp]# rm -rf *
[root@centos7 tmp]# mkdir a b c d test logdir shdir
[root@centos7 tmp]# touch "one space.log"
[root@centos7 tmp]# touch logdir/{1..10}.log
[root@centos7 tmp]# touch shdir/{1..5}.sh
[root@centos7 tmp]# echo "the second sh the second line" > shdir/2.sh 2.sh
[root@centos7 tmp]# cat << eof > shdir/1.sh
> the firsh sh
> the second line
> eof
[root@centos7 tmp]# 

1.1 分割行为

对于xargs , 它将接收到的stdout处理后传递到xargs后面的命令参数位 , 不写命令时默认的命令是echo

[root@centos7 tmp]# cat shdir/1.sh | xargs
the firsh sh the second line
[root@centos7 tmp]# cat shdir/1.sh | xargs echo 
the firsh sh the second line
[root@centos7 tmp]# 

将分行处理掉不是echo实现的 , 而是管道传递过来的stdin经过xargs处理后的 : 将所有空格、制表符和分行符都替换为空格并压缩到一行上显示 , 这一整行将作为一个整体 ,这个整体可能直接交给命令或者作为stdout通过管道传递给管道右边的命令, 这时结果将作为整体传递, 也可能被xargs同时指定的分批选项分批处理。

如果想要保存制表符、空格等特殊符号, 需要将它们用单引号或双引号包围起来, 但是单双引号(和反斜线)都会被xargs去掉。

单引号和双引号的存在让处理变的很不受控制 , 经常会影响正常的分割和处理。

如果不指定分批选项 , xargs的一整行结果将作为一个整体输出 , 而不是分隔开的。也许看处理的结果感觉是分开处理的 ,例如下面的第一个命令, 但是这是因为ls允许接受多个空格分开的参数, 执行第二个命令, 可以证明它确实是将整行作为整体传输给命令的。

[root@centos7 tmp]# find /tmp -maxdepth 1 | xargs ls
ls: cannot access /tmp/one: No such file or directory
ls: cannot access space.log: No such file or directory
/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  2.log  4.log  6.log  8.log
1.log   3.log  5.log  7.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@centos7 tmp]# find /tmp -maxdepth 1 | xargs -p ls
ls /tmp /tmp/.XIM-unix /tmp/.Test-unix /tmp/.X11-unix /tmp/.ICE-unix /tmp/.font-unix /tmp/a /tmp/b /tmp/c /tmp/d /tmp/test /tmp/logdir /tmp/shdir /tmp/one space.log ?...

如果对独立的xargs指定分批选项, 则有两种分批可能 : 指定-n时按空格分段 , 然后划批。 指定-L或者-i时按段划批,

[root@centos7 tmp]# ls
a  b  c  d  logdir  one space.log  shdir  test
[root@centos7 tmp]# ls | xargs -n 2
a b
c d
logdir one              #  one和space.log分割开了, 说明-n是按空格分割的
space.log shdir
test
[root@centos7 tmp]# ls | xargs -L 2
a b
c d
logdir one space.log    # one space.log作为一个分段, 文件名中的空格没有分割这个段
shdir test

[root@centos7 tmp]# ls | xargs -i -p echo {}
echo a ?...
echo b ?...
echo c ?...
echo d ?...
echo logdir ?...
echo one space.log ?...
echo shdir ?...
echo test ?...
[root@centos7 tmp]# 

1.1.1 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导致的分段空格 , 其余所有的符号都是分段中的一部分

    • 输出 : 最后根据指定的分批选项来输出。这里需要注意 , 分段前后有特殊符号时会完全按照符号输出

  • 从上面的阶段得出以下两结论

    • xargs -d会忽略文本意义上的符号。对于文本意义上的空格、制表符、分行符, 除非是-d指定的符号, 否则它们从来不会被处理 , 它们一直都是每个分段里的一部分 ;

    • 由于第一阶段标记意义的符号会替换为分行符号 , 所以传入的stdin的每个标记意义符号位都在最终的xargs -d 结果上分行了 , 但是它们已经是分段中的普通符号了 , 除非它们是-d指定的符号。

[root@centos7 tmp]# ls
a  b  c  d  logdir  one space.log  shdir  test
[root@centos7 tmp]# ls | xargs -d"o"
a
b
c
d
l gdir
 ne space.l g
shdir
test
​
[root@centos7 tmp]# 
​

[root@centos7 tmp]# ls | xargs -n 2 -d"o"
a
b
c
d
l gdir
​
ne space.l g
shdir
test
​
[root@centos7 tmp]# ls | xargs -n 2 -d"o" -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
​
​

1.1.2 xargs -0

xargs -0的行为和xargs -d基本是一样的 , 只是-d是指定分隔符 , -0是指定固定的\0作为分隔符。其实xargs -0就是特殊的xargs -d的一种 , 它等价于xargs -d"\0"

  • xargs -0行为如下:

    • xargs -0是分割阶段的选项, 所以它优先于分批选项(-n-L-i)

    • xargs -0不是先xargs-0处理的 , 它是区别于独立的xargs的另一个分割选项

    • xargs -0可以处理接收的stdin中的null字符(\0)。如果不使用-0选项或–null选项 , 检测到\0后会给出警告提醒 , 并只向命令传递非\0段。xargs -0–null是一样的效果。

  • xargs -0整体执行有几个阶段 :

    • 替换 : 将接收stdin的所有的标记意义的符号替换为\n , 替换完成后所有的符号(空格、制表符、分行符)变成字面意义上的普通符号 , 即文本意义的符号

    • 分段 : 将检测到的null字符 (\0) 使用标记意义上的空格来分段 , 由于分段前所有符号都是普通字面意义上的符号, 所以有的分段中可能包含了空格、制表符、分行符。也就是说除了 -0 导致的分段空格 , 其余所有的符号都是分段中的一部分

      如果没有检测到\0 , 则接收的整个stdin将成为一个不可分割的整体 , 任何分批选项都不会其分割开 ,因为它只有一个段。

    • 输出 : 最后根据指定的分批选项来输出。这里需要注意, 分段前后有特殊符号时会完全按照符号输出

根据上面的结论可知 , xargs -0会忽略所有文本意义上的符号 , 它的主要目的是处理\0符号。

[root@centos7 tmp]# ls | tr " " "\t" | xargs -0 
a
b
c
d
logdir
one    space.log
shdir
test
                #注意空行,因为命令结尾是标记意义上换行符号

[root@centos7 tmp]#  ls | tr " " " " | xargs -0 
a
b
c
d
logdir
one space.log
shdir
test
            # 注意有空行

如果检测到\0而没有使用-0–null处理则给出警告。注意警告后执行哪些文件

[root@centos7 tmp]# ls | tr " " "\0"
a
b
c
d
logdir
onespace.log
shdir
test
[root@centos7 tmp]# ls | tr " " "\0" | xargs
xargs: WARNING: a NUL character occurred in the input.  It cannot be passed through in the argument list.  Did you mean to use the --null option?
a b c d logdir one shdir test
​

再例如 , 将所有的换行符换成null字符 , 结果中除了最前面的字母a和由于空格而不被\0影响的space.log , 其余的由于全部有\0全部被忽略。

[root@centos7 tmp]# ls | tr "\n" "\0" 
abcdlogdirone space.logshdirtest[root@centos7 tmp]# ls | tr "\n" "\0" |xargs
xargs: WARNING: a NUL character occurred in the input.  It cannot be passed through in the argument list.  Did you mean to use the --null option?
a space.log
[root@centos7 tmp]# 
​

使用-0–null来解决问题 , 也可以使用等价的xargs -d"\0"来解决。

[root@centos7 tmp]# ls | tr "\n" "\0" |xargs -0
a b c d logdir one space.log shdir test
[root@centos7 tmp]# ls | tr "\n" "\0" |xargs -d"\0"
a b c d logdir one space.log shdir test

如果使用xargs -0时不指定分批选项(-n-L-i) , 则处理后的结果将作为一个整体输出。

如果指定了分批选项 , 并且检测到了null字符 , 则以\0位的空格分段划批 , 这时使用-n-L-i的结果是一样的。例如使用-n选项来观察是如何分批的。

[root@centos7 tmp]# ls | tr "\n" "\0" | xargs -0 -n 3
a b c
d logdir one space.log
shdir test

如果指定了分批选项 , 但没有检测到null字符 , 则整个结果将称为一个不可分割整体 , 这时使用分批选项是完全无意义的。

[root@centos7 tmp]# ls | xargs -0 -n 3 -p 
echo a
b
c
d
logdir
one space.log
shdir
test
 ?...

1.2 分批行为

1.2.1 xargs -n

xargs -n分两种情况 : 和独立的xargs一起使用 , 这时按照每个空格分段划批 ; 和xargs -d或xargs -0一起使用 ,这时按段分批 , 即不以空格 、制表符和分行符分段划批。

# 和独立的xargs一起使用, 以空格分段划批
[root@centos7 tmp]# ls | xargs -n 3 -p
echo a b c ?...
echo d logdir one ?...           ##  one 和 space.log 被割开了
echo space.log shdir test ?...
echo ?...
# 和xargs -d一起使用, 按段分批
[root@centos7 tmp]# ls | xargs -d"o" -n3 -p
echo a
b
c
d
l gdir
 ne space.l ?...         # 注意此行
echo g
shdir
test
 ?...

1.2.2 xargs -L

-n选项类似 , 唯一的区别是-L永远是按段划批 , 而-n在和独立的xargs一起使用时是按空格分段划批的。

该选项的一个同义词是-l , 但是man推荐使用-L替代-l , 因为-L符合POSIX标准 , 而-l不符合。使用–max-lines也可以。

也许你man xargs时发现-L选项是指定传递时最大传递行数量的 , man的结果如下图。但是通过下面的实验可以验证其实-L是指定传递的最大段数 , 也就是分批。

image-20210901160011314

#如果是指定传递的最大行数量,则一行就输出完了,这里却分了多行输出
[root@centos7 tmp]# ls | xargs -L 3 -p 
echo a b c ?...
echo d logdir one space.log ?...
echo shdir test ?...
#这就更能证明是指定最大传递的段数量了
[root@centos7 tmp]# ls | xargs -d"o" -L 3 -p 
echo a
b
c
d
l gdir
 ne space.l ?...
echo g
shdir
test
 ?...

1.2.3 参数替换: xargs -i 和xargs -I

xargs -i选项在逻辑上用于接收传递的分批结果。

如果不使用-i , 则默认是将分割后处理后的结果整体传递到命令的最尾部。但是有时候需要传递到多个位置 , 不使用-i就不知道传递到哪个位置了 , 例如重命名备份的时候在每个传递过来的文件名加上后缀.bak , 这需要两个参数位。

使用xargs -i时以大括号{}作为替换符号 , 传递的时候看到{}就将被结果替换。可以将{}放在任意需要传递的参数位上 , 如果多个地方使用{}就实现了多个传递。

xargs -I(大写字母i)和xargs -i是一样的 , 只是-i默认使用大括号作为替换符号 , -I则可以指定其他的符号、字母、数字作为替换符号 , 但是必须用引号包起来。man推荐使用-I代替-i , 但是一般都使用-i图个简单 , 除非在命令中不能使用大括号 , 如touch {1..1000}.log时大括号就不能用来做替换符号。

[root@centos7 tmp]# ls ./logdir/
10.log  1.log  2.log  3.log  4.log  5.log  6.log  7.log  8.log  9.log
[root@centos7 tmp]# ls logdir/ | xargs  -i mv ./logdir/{} ./logdir/{}.bak
[root@centos7 tmp]# ls ./logdir/
10.log.bak  1.log.bak  2.log.bak  3.log.bak  4.log.bak  5.log.bak  6.log.bak  7.log.bak  8.log.bak  9.log.bak
​

由于-i选项是按分段来传递的。所以尽管看上去等价的xargs echoxargs -i echo {}并不等价。

[root@centos7 tmp]# ls | xargs echo 
a b c d logdir one space.log shdir test
[root@centos7 tmp]# ls | xargs -i  echo {}  
a
b
c
d
logdir
one space.log
shdir
test

既然使用-i后是分段传递的 , 这就意味着指定了它就无法实现按批传递多个参数了 ; 并且如果使用多个大括号 , 意味着必须使用-i , 那么也无法分批传递。

例如 , 想将数字1-103个数显示在startend之间。效果如下 :

start 1 2 3 end
start 4 5 6 end
start 7 8 9 end
start 10 end

由于指定了参数传递位置 , 所以必须使用-i , 那么就无法一次传递3个数。要解决这个问题 , 就要想办法让每三个数分一次段然后使用-i传递 , 方法也就随之而来了。可以将每三个数分一次行写入一个文件。如 :

$ cat <<eof>logdir/1.log
> 1 2 3
> 4 5 6
> 7 8 9
> 10
> eof

再使用xargs -i分批传递。

$ cat logdir/1.log | xargs -i echo "start {} end"
start 1 2 3 end
start 4 5 6 end
start 7 8 9 end
start 10 end

也可以使用多次xargs。很多时候无法解决分段的问题都可以通过多次使用xargs来解决。

$ echo {1..10} | xargs -n 3 | xargs -i echo "start {} end"

1.2.4 分批选项生效规则

-i-L-n选项都是分批选项。它们的生效规则是 , 谁指定在后面 , 谁就生效。

下面-i放在-n-L之后 , 结果是-n-L被忽略。

[root@centos7 tmp]# ls | xargs -d"o" -n 2 -p  -i echo {}
echo a
b
c
d
l ?...                    # 说明是一段一段输出, 而不是两段一批输出, 即-n选项被忽略
echo gdir
 ?...
echo ne space.l ?...
echo g
shdir
test
 ?...
# 和上面的结果是一模一样的, 说明-L选项被忽略
[root@centos7 tmp]# ls | xargs -d"o" -L 3 -p  -i echo {}
echo a
b
c
d
l ?...
echo gdir
 ?...
echo ne space.l ?...
echo g
shdir
test
 ?...

下面是-L放在-n后 , 结果是-n被忽略。

# 结果也是一段一段输出的, 说明-n选项被忽略  
[root@centos7 tmp]# ls | xargs -d"o" -n 2 -p -L 1 echo 
echo a
b
c
d
l ?...
echo gdir
 ?...
echo ne space.l ?...
echo g
shdir
test
 ?...

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

1.2.5. 典型应用

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

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

[root@centos7 tmp]$ rm -fr /tmp/*.log
-bash: /bin/rm: Argument list too long

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

[root@centos7 ~]$ cd /tmp/ && ls | xargs -n 10000 rm -rf

如果不使用分批直接交给rm -rf也是一样可以执行成功的。

[root@centos7 ~]$ cd /tmp/ && ls | xargs rm -rf

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

[root@centos7 tmp]# ll 
total 0
drwxr-xr-x 2 root root   6 Aug 26 21:52 a
drwxr-xr-x 2 root root   6 Aug 26 21:52 b
drwxr-xr-x 2 root root   6 Aug 26 21:52 c
drwxr-xr-x 2 root root   6 Aug 26 21:52 d
drwxr-xr-x 2 root root 177 Sep  1 16:14 logdir
-rw-r--r-- 1 root root   0 Aug 26 21:52 one space.log
drwxr-xr-x 2 root root  66 Aug 26 21:53 shdir
drwxr-xr-x 2 root root   6 Aug 26 21:52 test
[root@centos7 tmp]$ ll /tmp/longshuai/ | grep "^-" | wc -l

1.3 终止行为

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

终止行为支持的参数有两个 : -E,-e-e选项也是 , 但是官方建议使用-E替代-e , 因为-EPOSIX标准兼容的, 而-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@centos7 tmp]# ls 
a  b  c  d  logdir  one space.log  shdir  test
​
[root@centos7 tmp]# ls |xargs -E one
a b c d logdir
​
[root@centos7 tmp]# ls | xargs -n 2 -E one
a b
c d
logdir
​
[root@centos7 tmp]# ls | xargs -L 2 -E one
a b
c d
logdir
​
[root@centos7 tmp]# ls | xargs -i -E"one space.log" echo {}
a
b
c
d
logdir
​
[root@centos7 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 test ?...

1.4 执行过程

使用-p选项是交互询问式的 , 只有每次询问的时候输入y(或yes)才会执行 , 直接按enter键是不会执行的。

使用-t选项是在每次执行xargs后面的命令都会先在stderr上打印一遍命令的执行过程然后才正式执行。

使用-p-t选项就可以根据xargs后命令的执行顺序进行推测 , xargs是如何分段、分批以及如何传递的 ,这通过它们有助于理解xargs的各种选项。

[root@centos7 tmp]# ls | xargs -t -n2 
echo a b 
a b
echo c d 
c d
echo logdir one 
logdir one
echo space.log shdir 
space.log shdir
echo test 
test
​
[root@centos7 tmp]# ls | xargs -p -n2 
echo a b ?...y
a b
echo c d ?...y
echo logdir one ?...c d
y
echo space.log shdir ?...logdir one
y
space.log shdir
echo test ?...y
test
​

2. xargs处理总结

2.1 xargs与find的结合

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

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

[root@centos7 tmp]# touch space.log
[root@centos7 tmp]# touch one
[root@centos7 tmp]# ls -m 
a, b, c, d, logdir, one, one space.log, shdir, space.log, test

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

[root@centos7 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的整体性。因为-print0one space.log的前后各有一个\0 , 但是文件名中间没有。

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

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

# 随意指定非文件名中的字符都行, 不一定非要\0
[root@centos7 tmp]# find -name "* *.log" -print0 | xargs -d"x" rm -rf
#可以用x原因是由于文件名one space.log文件中并没有x这个字母,分段时可以直接分为一段,如果使用文件中的字母直接将文本分为两段, 就没法使用这个命令了。

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

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

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

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

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

2.2 xargs 默认选项 : -s

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

[root@centos7 tmp]# cd logdir/
[root@centos7 logdir]# touch {1..1000000}
-bash: /usr/bin/touch: Argument list too long 
# 执行的时候记得使用-p选项, 否则慢慢等吧。
$ echo {1..1000000} | xargs touch

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

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

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

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

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

2.3 xargs不足之处

其实是xargs的限制和缺点 , 但因为通过-i选项方便演示 , 所以此处使用-i选项。注意 , 不是-i选项的缺陷。

由于xargs -i传递数据时是在shell执行xargs命令的时候 , xargs后的命令如果有依赖于待传递数据的表达式 , 则无法正确执行。

例如, 无法通过xargs传递数值做正确的算术扩展:

$ echo 1  | xargs  -I "x" echo $((2*x))
0

无法将数据传递到命令替换中。

$ echo /etc/fstab | xargs -i `cat {}`     
cat: {}: No such file or directory

这时要通过xargs正确实现目标 , 只能改变方法或寻找一些小技巧, 例如:

$ echo 1 | xargs -i expr 2 \* {}
2
$ echo /etc/fstab | xargs -i cat $(echo {})

另外 , xargs无法处理bash内置命令。例如:

$ echo /etc  | xargs -i cd {}
xargs: cd: No such file or directory


参考链接

Linux xargs命令详解 | 骏马金龙 (junmajinlong.com)


熊熊