热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

logger参数列表过长_[源码级解析]巧妙解决并深度分析Linux下rm命令提示参数列表过长的问题...

在维护实习单位服务器的过程中,偶然发现一个有350万文件的文件夹需要清理,于是我习惯性执行了rm-rf.*,却在数秒后被告知“参数列表过长

0ab300abbac2abdd5d39ae90987c07af.png
在维护实习单位服务器的过程中,偶然发现一个有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 &#61; count(argv, MAX_ARG_STRINGS);if (bprm->argc <0)return bprm->argc;bprm->envc &#61; 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&#43;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 &#61; _STK_LIM / 4 * 3;limit &#61; min(limit, bprm->rlim_stack.rlim_cur / 4);/** We&#39;ve historically supported up to 32 pages (ARG_MAX)* of argument strings even with small stacks*/limit &#61; 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&#39;t stored until much later when we can&#39;t* signal to the parent that the child has run out of stack space.* Instead, calculate it here so it&#39;s possible to fail gracefully.*/ptr_size &#61; (bprm->argc &#43; bprm->envc) * sizeof(void *);if (limit <&#61; ptr_size)return -E2BIG;limit -&#61; ptr_size;bprm->argmin &#61; bprm->p - limit;return 0;
}

从完整的注释中我们可以得知&#xff0c;限制最大参数长度的参数叫做ARG_MAX&#xff0c;而且其大小为栈的1/4&#xff08;可能是为了保证参数以外还有多的空间可以存储其他数据&#xff09;。当然&#xff0c;如果你是个考古爱好者&#xff0c;你会发现在2.6版本内核&#xff08;低于2.6.22&#xff09;的include/linux/limits.h文件中&#xff0c;ARG_MAX是一个写死的常量 &#xff1a;考古链接。

关于ARG_MAX我们可以通过Linux下的getconf命令来获取&#xff0c;这是一个获取Linux下所有全局变量/常量的命令&#xff1a;

$ getconf ARG_MAX
2097152

我们再查询当前配置的栈大小&#xff1a;

$ ulimit -s
8192

ARG_MAX参数的单位是Byte&#xff0c;ulimit -s命令的单位是MB&#xff0c;可以看到当前最大参数数量的确是栈空间的1/4。那如果我们把栈空间增大呢&#xff1f;

$ ulimit -s 81920
$ ulimit -s
81920
$ getconf ARG_MAX
20971520

可以看到&#xff0c;允许的最大参数数量立马随着栈空间增大而同步增大。这个时候我们再来删除之前那个大目录&#xff0c;就不会出现『参数列表过长』的错误提示了。

实际上这一限制在大多数现代操作系统中均存在&#xff08;例如MacOS、Windows等&#xff09;&#xff0c;可参考此处获得更多信息&#xff1a;ARG_MAX, maximum length of arguments for a new process



推荐阅读
  • 程序员如何选择机械键盘轴体?红轴和茶轴对比
    本文介绍了程序员如何选择机械键盘轴体,特别是红轴和茶轴的对比。同时还介绍了U盘安装Linux镜像的步骤,以及在Linux系统中安装软件的命令行操作。此外,还介绍了nodejs和npm的安装方法,以及在VSCode中安装和配置常用插件的方法。最后,还介绍了如何在GitHub上配置SSH密钥和git的基本配置。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文介绍了Linux Shell中括号和整数扩展的使用方法,包括命令组、命令替换、初始化数组以及算术表达式和逻辑判断的相关内容。括号中的命令将会在新开的子shell中顺序执行,括号中的变量不能被脚本余下的部分使用。命令替换可以用于将命令的标准输出作为另一个命令的输入。括号中的运算符和表达式符合C语言运算规则,可以用在整数扩展中进行算术计算和逻辑判断。 ... [详细]
  • 本文总结了Linux下多线程执行shell脚本的4种方法,包括切换到工作目录执行、使用绝对路径执行、直接使用bash或sh执行。同时介绍了为什么需要加上"./"来执行脚本的原因。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • Linux服务器密码过期策略、登录次数限制、私钥登录等配置方法
    本文介绍了在Linux服务器上进行密码过期策略、登录次数限制、私钥登录等配置的方法。通过修改配置文件中的参数,可以设置密码的有效期、最小间隔时间、最小长度,并在密码过期前进行提示。同时还介绍了如何进行公钥登录和修改默认账户用户名的操作。详细步骤和注意事项可参考本文内容。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了Oracle数据库中tnsnames.ora文件的作用和配置方法。tnsnames.ora文件在数据库启动过程中会被读取,用于解析LOCAL_LISTENER,并且与侦听无关。文章还提供了配置LOCAL_LISTENER和1522端口的示例,并展示了listener.ora文件的内容。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • 在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的步骤和方法
    本文介绍了在CentOS/RHEL 7/6,Fedora 27/26/25上安装JAVA 9的详细步骤和方法。首先需要下载最新的Java SE Development Kit 9发行版,然后按照给出的Shell命令行方式进行安装。详细的步骤和方法请参考正文内容。 ... [详细]
  • Linux批量复制并重命名和批量复制文件到多个文件夹的方法
    本文介绍了在Linux系统下批量复制并重命名文件以及批量复制文件到多个文件夹的方法。通过使用模式匹配功能,可以方便地实现文件的批量操作。同时,还提供了有关shell语法中的()和{}的参考资料。 ... [详细]
  • Tomcat安装与配置教程及常见问题解决方法
    本文介绍了Tomcat的安装与配置教程,包括jdk版本的选择、域名解析、war文件的部署和访问、常见问题的解决方法等。其中涉及到的问题包括403问题、数据库连接问题、1130错误、2003错误、Java Runtime版本不兼容问题以及502错误等。最后还提到了项目的前后端连接代码的配置。通过本文的指导,读者可以顺利完成Tomcat的安装与配置,并解决常见的问题。 ... [详细]
  • Linux Shell脚步的格式
    Shell脚步等多个命令的组合,可以做成一个shell文件(1.sh)赋权执行执行命令的方式前两张新的进程中执行,对当前进程不产生影响(cdtmp;pwds ... [详细]
author-avatar
育霖培伦861
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有