聊聊 Linux Argument list too long 问题的N种解法

文章目录

        • 一、遇到问题
        • 二、问题原因
        • 三、准备测试环境
        • 四、低效解决办法
          • 1、shell 循环删除
            • 1.1 删除方法解析
            • 1.2 删除速度实测
          • 2、 find -exec
            • 2.1 删除方法解析
            • 2.2 删除速度实测
          • 3、ls 管道 + bash
            • 3.1 删除方法解析
            • 3.2 删除速度实测
          • 4、find 管道 + bash
            • 4.1 删除方法解析
            • 4.2 删除速度实测
        • 五、高效解决办法
          • 1、rsync --delete
            • 1.1 删除方法解析
            • 1.2 删除速度实测
          • 2、find -delete
            • 2.1 删除方法解析
            • 2.2 删除速度实测
          • 3、find 管道 + xargs
            • 3.1 删除方法解析
            • 3.2 删除速度实测
        • 六、最呆萌解决办法: 手动将文件群分组处理
        • 七、最折腾解决办法: 重新编译内核
        • 八、删除 30万 个文件效率对比




一、遇到问题


作为一个 Linux 玩家,想必你也曾遇到过以下错误提示:

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



二、问题原因


Argument list too long” 这个错误,字面意思是参数列表过长
通常在用户在一行命令中提供了过多的参数(比如 ls/cp/rm * 等) 的时候出现;

那么,何为过长?
或者说提供多少个参数才不会触发 “Argument list too long” 呢?

可以通过 getconf ARG_MAX 查看 Linux 系统的这个限制:

:~> getconf ARG_MAX
2097152



三、准备测试环境


由于实测创造几百万个文件比较耗时,
为了加快测试节奏,减少等待时间,
这里将测试删除的样本文件数,设计为: 30万个

产生 30万 个测试文件的方法如下:

time for i in `seq 1 300000`; do echo 0 > test$i; done



四、低效解决办法


如果不讲究删除速度的话,这里介绍几种比较低效的删除方法。


1、shell 循环删除

1.1 删除方法解析

实现一个 shell 循环,来逐个删除,也算是个比较低效的办法;

for i in $(echo *)
do 
    rm  -f  $i
done

1.2 删除速度实测

实测删除 30 万个文件,耗时为: 83m48.477s

time  for i in $(echo *);do rm  -f  $i; done;
real	83m48.477s
user	42m55.000s
sys	    21m50.788s

2、 find -exec

2.1 删除方法解析
find . -type f -exec rm -v {} \;

通过 find 命令,将文件清单输出到 rm 命令删除;
如果文件名有空格等特殊字符,该方法依然能处理。


该方法一次只能处理一个,因此显著的缺点是比较耗费时间。
逐个处理的参数传递过程,可以通过如下命令来验证:

find . -type f -exec echo begin {} \;
begin ./test5
begin ./test3
begin ./test2
begin ./test4
begin ./test1

2.2 删除速度实测

实测删除 30 万个文件,耗时为: 81m31.770s

time  find . -type f -exec rm -v {} \;
real	81m31.770s
user	42m33.216s
sys	    20m23.044s

3、ls 管道 + bash

3.1 删除方法解析
 ls -l | awk  '{print "rm -f " $NF}'  | sh 

通过 ls 遍历文件,拼凑好每个文件的删除命令,然后通过管道传给 sh 执行删除。


3.2 删除速度实测

实测删除 30 万个文件,耗时为: 81m14.443s

time  ls -l | awk  '{print "rm -f " $NF}'   |sh
real	81m14.443s
user	44m38.216s
sys	    18m26.060s

4、find 管道 + bash

4.1 删除方法解析
 find . -type f   |  awk  '{print "rm -f " $1}'  |  sh 

通过 find 遍历文件,拼凑好每个文件的删除命令,然后通过管道传给 sh 执行删除。


4.2 删除速度实测

实测删除 30 万个文件,耗时为: 79m59.313s

time  find . -type f   |  awk  '{print "rm -f " $1}'  |  sh 
real	79m59.313s
user	44m27.916s
sys	    16m52.244s



五、高效解决办法


这时候会发现低效的删除方法实在太慢了,删 30 万个文件,得等一个多小时,这没法等了。

所以,这里再分享几种比较高效的删除方法。


1、rsync --delete

1.1 删除方法解析
rsync --help | grep delete
     --del                   an alias for --delete-during
     --delete                delete extraneous files from destination dirs
     --delete-before         receiver deletes before transfer, not during
     --delete-during         receiver deletes during the transfer
     --delete-delay          find deletions during, delete after
     --delete-after          receiver deletes after transfer, not during
     --delete-excluded       also delete excluded files from destination dirs

删除举例:

`rsync  --delete-before  -avH  --progress --stats /tmp/testtmp/ /tmp/test`

上面各个参数的解释如下:

-delete-before 接收者在传输之前进行删除操作
–progress 在传输时显示传输过程
-a 归档模式,表示以递归方式传输文件,并保持所有文件属性
-H 保持硬连接的文件
-v 详细输出模式
–stats 给出某些文件的传输状态


这里需要注意:
使用上面命令清理时,目标目录的权限是和源目录的权限一样的。
比如:/tmp/testtmp/ 是 root:root
而 /tmp/test 之前是 test:test
执行之后 /tmp/test 目录的权限也会变成 root:root

这是因为 rsync-a-rlptogD 这些参数的集合,
如果不希望出现这种权限效果,可以将 og(owner:group)两个参数去掉。
这样执行清空后,就能自动保持之前的目录权限,如下:

rsync  --delete-before  -rlptD  /tmp/testtmp/ /tmp/test

通过这种 rsync 方法删除内容时,是通过事先创建好空目录,用来覆盖替换掉需要删除的内含大量文件的目录,因此开销较小。


1.2 删除速度实测

实测删除 30 万个文件,耗时为: 0m51.322s

//先试试 --delete-before :
mkdir  /tmp/testtmp/ ;
time rsync --delete-before -avH --progress --stats  /tmp/testtmp/ /tmp/test
real	0m51.322s
user	0m0.200s
sys	    0m50.096s

//换 --delete 再试试,差不多:
time  rsync  --delete   -rlptD  /tmp/testtmp/ /tmp/test
real	0m51.547s
user	0m0.184s
sys	    0m51.032s


2、find -delete

2.1 删除方法解析
 find . -type f -delete

这里需要注意的是, 如果被删除的目录,包含其他文件的话,则不能直接删除:

 find . -type d  -print  -delete   // -print 输出删除文件的信息
./test
find: cannot delete './test': Directory not empty

2.2 删除速度实测

实测删除 30 万个文件,耗时为: 0m3.454s优秀!

 time find . -type f -delete
real	0m3.454s
user	0m0.164s
sys 	0m2.940s

3、find 管道 + xargs

3.1 删除方法解析
 find . -name "*" | xargs rm -rf

3.2 删除速度实测

实测删除 30 万个文件,耗时为: 0m2.970s优秀!

 time  find . -name "*" | xargs rm -rf
real	0m2.970s
user	0m2.852s
sys  	0m0.304s



六、最呆萌解决办法: 手动将文件群分组处理


操作方法:

rm -rf   [a-c]*  
rm -rf   202004*  
...

该方法只是简单粗暴地通过手工分组,来使参数数量符合 Linux 系统要求 ,可能需要操作多次;
适用场景比较有限,只适用于要处理的文件名字分布比较均匀且有规律的情况。

一般情况下,
出现数百万文件的时候,文件名分布规律不明显的,而且其实也不方便摸清这个分布规律。
因此,该方法也许可以认为是最耗费时间的方法了。




七、最折腾解决办法: 重新编译内核


如果要发扬折腾精神,不怕麻烦不怕折腾且风险可控的话,
可以考虑手动调大内核中分配给命令行参数的页数,并重新编译内核。

由于该方法涉及内核源代码修改,因此提醒大家: 该方法在生产环境中必须小心测试,谨慎使用


具体操作方法如下:

打开内核源代码的 include/linux/binfmts.h 文件,找到如下行:

/*
* MAX_ARG_PAGES defines the number of pages allocated for arguments
* and envelope for the new program. 32 should suffice, this gives
* a maximum env+arg of 128kB w/4KB pages!
*/
#define MAX_ARG_PAGES 32

MAX_ARG_PAGES 数值调大为 64 或者更大的数值,
修改好后,重新编译并启用新内核,
即可解决 “Argument list too long” 参数受限问题。


该方法虽然折腾,
但是如果确认风险可控的话,实现起来也可以蛮快的,至少会比手工分组来删快些吧…




八、删除 30万 个文件效率对比


解决方法 删除 30万 文件耗时
find 管道 + xargs 2.970s
find -delete 3.454s
rsync --delete 51.322s
find 管道 + bash 79m59.313s
ls 管道 + bash 81m14.443s
find -exec 81m31.770s
shell 循环删除 83m48.477s
重新编译内核 猜测较慢
手工分组处理 猜测最慢

所以,
find 配合 xargs 或者 自己的 -delete参数,威力巨大,是当之无愧的 大量文件高效删除专家!




参考文档: Deleting tons of files in Linux (Argument list too long)




你可能感兴趣的:(运维进阶:Linux系统管理,linux,shell,运维)