作为一个 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
如果不讲究删除速度的话,这里介绍几种比较低效的删除方法。
实现一个 shell 循环,来逐个删除,也算是个比较低效的办法;
for i in $(echo *)
do
rm -f $i
done
实测删除 30 万个文件,耗时为: 83m48.477s
time for i in $(echo *);do rm -f $i; done;
real 83m48.477s
user 42m55.000s
sys 21m50.788s
find . -type f -exec rm -v {} \;
通过 find
命令,将文件清单输出到 rm
命令删除;
如果文件名有空格等特殊字符,该方法依然能处理。
该方法一次只能处理一个,因此显著的缺点是比较耗费时间。
逐个处理的参数传递过程,可以通过如下命令来验证:
find . -type f -exec echo begin {} \;
begin ./test5
begin ./test3
begin ./test2
begin ./test4
begin ./test1
实测删除 30 万个文件,耗时为: 81m31.770s
time find . -type f -exec rm -v {} \;
real 81m31.770s
user 42m33.216s
sys 20m23.044s
ls -l | awk '{print "rm -f " $NF}' | sh
通过 ls
遍历文件,拼凑好每个文件的删除命令,然后通过管道传给 sh 执行删除。
实测删除 30 万个文件,耗时为: 81m14.443s
time ls -l | awk '{print "rm -f " $NF}' |sh
real 81m14.443s
user 44m38.216s
sys 18m26.060s
find . -type f | awk '{print "rm -f " $1}' | sh
通过 find
遍历文件,拼凑好每个文件的删除命令,然后通过管道传给 sh 执行删除。
实测删除 30 万个文件,耗时为: 79m59.313s
time find . -type f | awk '{print "rm -f " $1}' | sh
real 79m59.313s
user 44m27.916s
sys 16m52.244s
这时候会发现低效的删除方法实在太慢了,删 30 万个文件,得等一个多小时,这没法等了。
所以,这里再分享几种比较高效的删除方法。
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 方法删除内容时,是通过事先创建好空目录,用来覆盖替换掉需要删除的内含大量文件的目录,因此开销较小。
实测删除 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
find . -type f -delete
这里需要注意的是, 如果被删除的目录,包含其他文件的话,则不能直接删除:
find . -type d -print -delete // -print 输出删除文件的信息
./test
find: cannot delete './test': Directory not empty
实测删除 30 万个文件,耗时为: 0m3.454s , 优秀!
time find . -type f -delete
real 0m3.454s
user 0m0.164s
sys 0m2.940s
find . -name "*" | xargs rm -rf
实测删除 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万 文件耗时 |
---|---|
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)