logger 参数列表过长_[源码级解析] 巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...
在维护实习单位服务器的过程中,偶然发现一个有350万文件的文件夹需要清理,于是我习惯性执行了
rm -rf ./*
,却在数秒后被告知“参数列表过长”。在一番折腾过后,我终于通过取巧的办法完成了这一任务,也随着相关内核源码的阅读,了解到了关于Linux Shell的一些有趣特性。
本文原载于未命名小站,由作者本人同步至知乎,转载请注明原作者博客地址或本链接,谢谢!
0x01
最初我以为是rm
命令对文件数量存在限制,但当我尝试ls ./*
、touch ./*
等命令都遇到这一问题之后,我开始将注意力放在Bash本身上。也许是通配符的限制。
突然,我想起来rm
命令支持管道送入参数,也许可以通过这样的办法变相完成任务。于是我在另一个目录做了个测试:
$ touch 123-1
$ touch 123-2
$ echo "123-1 123-2" | xargs rm
这两个文件果然消失。
于是我尝试使用find将目录下面所有文件列出:
find . -name "*" | more # 使用more避免350多万个文件把终端挤崩溃
find . -name "*" | wc -l # 大致了解文件个数
输出的文件个数与我通过ls -l
命令输出的个数基本一致,果然输出了所有文件。接下来要做的就是将这些文件送到rm
中。
0x02
但事实并非如此简单,当我执行以下命令,以为可以一口气顺利删除所有文件的时候,我傻眼了:
$ find . -name "*" | xargs rm
rm: log: No such file or directory
rm: 20190601-110204.log: No such file or directory
... # 所有待删除文件均发生报错
我重新观察文件名,发现文件名格式均为log yyyymmdd-hhmmss.log
,众所周知Bash靠空格分割参数,文件名被传入rm的时候照着空格被截断,成为了两个文件名,难怪删除失败!
0x03
吸取教训,我使用了一个新的参数:
find . -name "*" -print0 | xargs -0 rm
注意这一参数里的-print0
与-0
,这是为了区分空格与分界符,加入参数后此前用于分隔参数的空格(0x0a
)则会变成NUL(0x00
),这一参数的效果可以通过16进制查看器体现:
$ ls
123 321
123 322
$ find . -name "12*" > 1.log
$ find . -name "12*" -print0 > 2.log
$ hexdump -C 1.log
00000000 2e 2f 31 32 33 20 33 32 31 0a 2e 2f 31 32 33 20 |./123 321../123 |
00000010 33 32 32 0a |322.|
00000014
$ hexdump -C 2.log
00000000 2e 2f 31 32 33 20 33 32 31 00 2e 2f 31 32 33 20 |./123 321../123 |
00000010 33 32 32 00 |322.|
00000014
可以发现在两个不同输出模式下,分隔符不一样。默认的分隔符与空格一致,即0x0a
,但当开启-print0
模式后,分隔符变成了0x00
,配合管道接收端的-0
参数将NUL
字符正确解析成参数定界符,则可以完成带空格文件名的正常解析。
解决了这一问题,我们再次执行,问题随即解决。
0x04
过了两天,又有一台服务器需要删除大量文件,且领导要求只删除文件不删除里面的目录,我一看,又是400多万个文件。但在之前的折腾过程中,我早有准备。
find
命令默认开启递归模式,如果只删除文件不删除目录,只需要配置递归深度为1即可:
find . -maxdepth 1 -name "*" -print0 | xargs -0 rm
执行命令后再执行ls -l
,发现问题果然解决,所有目录完好无损。
0x05
快速解决完问题后,我一看离下班还有好一阵子,便开始琢磨Bash内通配符的长度限制到底从哪来。
我首先找了另一个有大量文件的目录开始实验,换用zsh进行ls ./*
操作,得到的确是一样结果。看来该长度限制与Bash无关。
我突然想起来曾经看过的一个安全类视频:Youtube - Bash injection without letters or numbers - 33c3ctf hohoho (misc 350) - LiveOverFlow,其中解释了通配符(Wildcard)的原理。
当我们输入*
的时候,Shell所做的是列举出满足通配符规则的所有文件,并以空格分割,然后送进Shell。举例而言,如果你有一个全是txt文件的目录,你直接执行*
,就会发现以下错误:
$ touch abc.txt
$ touch bbc.txt
$ *
bash: command not found: abc.txt
$ echo *
abc.txt bbc.txt
相信看到这里,大家都明白通配符的行为以及为什么上述示例会报错,在上述示例中,Shell将abc.txt
看做命令,而将bbc.txt
看做参数。这也说明了通配符的行为:将满足条件的文件输出为文本(并默认输出到Shell)。
0x06
当我们继续向下挖掘,我们会想到Shell执行命令的本质:exec()
类系统调用。这一限制如果并非来自于Shell(因为find . -name "*"
并不会报错),那么就一定来自于系统调用。而一个Shell命令被执行的第一站则是exec()
及其六个实际调用:execl(),execle(),execlp(),execv(),execvp(),execve()
。
于是我们使用strace
工具来检查所有系统调用:
$ ls
1234.txt
123.txt
$ strace ls *
execve("/usr/bin/ls", ["ls", "1234.txt", "123.txt"], [/* 28 vars */]) = 0
...
看到这里,相信读者已经心里有数了,我们的命令与参数都被作为execve()
函数的第二个参数,以数组形式被传入。考虑到数组默认存储在栈中,该限制是否来自于Shell对栈空间的限制呢?
我查阅了Linux的源码,在fs/exec.c:478
中找到了我要的内容:源码
static int prepare_arg_pages(struct linux_binprm *bprm,struct user_arg_ptr argv, struct user_arg_ptr envp)
{unsigned long limit, ptr_size;bprm->argc = count(argv, MAX_ARG_STRINGS);if (bprm->argc < 0)return bprm->argc;bprm->envc = count(envp, MAX_ARG_STRINGS);if (bprm->envc < 0)return bprm->envc;/** Limit to 1/4 of the max stack size or 3/4 of _STK_LIM* (whichever is smaller) for the argv+env strings.* This ensures that:* - the remaining binfmt code will not run out of stack space,* - the program will have a reasonable amount of stack left* to work from.*/limit = _STK_LIM / 4 * 3;limit = min(limit, bprm->rlim_stack.rlim_cur / 4);/** We've historically supported up to 32 pages (ARG_MAX)* of argument strings even with small stacks*/limit = max_t(unsigned long, limit, ARG_MAX);/** We must account for the size of all the argv and envp pointers to* the argv and envp strings, since they will also take up space in* the stack. They aren't stored until much later when we can't* signal to the parent that the child has run out of stack space.* Instead, calculate it here so it's possible to fail gracefully.*/ptr_size = (bprm->argc + bprm->envc) * sizeof(void *);if (limit <= ptr_size)return -E2BIG;limit -= ptr_size;bprm->argmin = bprm->p - limit;return 0;
}
从完整的注释中我们可以得知,限制最大参数长度的参数叫做ARG_MAX
,而且其大小为栈的1/4
(可能是为了保证参数以外还有多的空间可以存储其他数据)。当然,如果你是个考古爱好者,你会发现在2.6版本内核(低于2.6.22)的include/linux/limits.h
文件中,ARG_MAX
是一个写死的常量 :考古链接。
关于ARG_MAX
我们可以通过Linux下的getconf
命令来获取,这是一个获取Linux下所有全局变量/常量的命令:
$ getconf ARG_MAX
2097152
我们再查询当前配置的栈大小:
$ ulimit -s
8192
ARG_MAX
参数的单位是Byte,ulimit -s
命令的单位是MB,可以看到当前最大参数数量的确是栈空间的1/4
。那如果我们把栈空间增大呢?
$ ulimit -s 81920
$ ulimit -s
81920
$ getconf ARG_MAX
20971520
可以看到,允许的最大参数数量立马随着栈空间增大而同步增大。这个时候我们再来删除之前那个大目录,就不会出现『参数列表过长』的错误提示了。
实际上这一限制在大多数现代操作系统中均存在(例如MacOS、Windows等),可参考此处获得更多信息:ARG_MAX, maximum length of arguments for a new process
logger 参数列表过长_[源码级解析] 巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...相关推荐
- 前端遍历列表生成表格_源码剖析狗屁不通文章生成器
前段时间,有一个叫"狗屁不通文章生成器"的项目一下子吸引了大家的兴趣,还引起各大科技媒体:36Kr.新浪专栏IT之家都不约而同的刊文报道.然而,综看整个项目,除去README.LI ...
- 集合的get方法中参数从多少开始_源码分析CopyOnWriteArrayList 中的隐藏知识,你Get了吗?...
欢迎点击 "未读代码" ,关注公众号,文章每周更新 杭州-阿里园区墙 前言 本觉 CopyOnWriteArrayList 过于简单,寻思看名字就能知道内部的实现逻辑,所以没有写这 ...
- 框架源码专题:Mybatis启动和执行流程、源码级解析
文章目录 1. Mybatis 启动流程 步骤一: 把xml配置文件解析成Configuration类 步骤二: 创建SqlSession会话 mybatis的三种执行器 步骤三: 在sqlSessi ...
- Asynctask源码级解析,深度探索源码之旅
常用方法: 1.onPreExecute方法是预加载,在主线程中执行 2.doInBackground运行在子线程中,执行耗时的操作,子线程中不能更新主UI界面 3.onProgressUpdate方 ...
- linux下perl命令行参数,Perl One-Liners | Perl命令行学习1 -e参数
注:本内容需要点的perl编程基础,最好是读过<perl语言入门>. 本系列是自己平常学习工作中的总结,每一个实例均为我为了讲解而设置的,自己试过的,如有错误,望能见谅 Perl 命令行参 ...
- RocketMQ 消费者Rebalance 解析——图解、源码级解析
- RocketMQ 容错策略 解析——图解、源码级解析
- RocketMQ 消息路由解析——图解、源码级解析
- RocketMQ 顺序消息解析——图解、源码级解析
最新文章
- BERT的通俗理解 预训练模型 微调
- Java项目:日历万年历(java+swing)
- ipmi重启_重启ipmi服务器
- 访问“ for”循环中的索引?
- 服务器被攻击怎么办 安全狗来防护
- centos 对已有卷扩容_centos7下对原有磁盘分区进行在线扩容
- Pytorch数据读取(Dataset, DataLoader, DataLoaderIter)
- makefile中的wildcard和notdir和patsubst
- 视频和视频帧:H264编码格式整理
- 海康rtsp抓包分析
- 电力载波通信了解笔记
- 走进小作坊(八)----公益之痒
- linux ubuntu 五笔输入法,ubuntu下安装fcitx五笔输入法
- G6 3.1 线条的属性
- SolidWorks参数化设计中Excel的应用
- 如何利用等比频宽公式将信号分解成部分重叠的子频段
- Adobe无法安装错误代码127
- rOG魔霸新锐2022和魔霸6区别 哪个好
- linux驱动面试题目汇总
- mysql计算员工的日薪_mysql 综合练习(一)