日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

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

發布時間:2024/7/23 linux 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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命令提示参数列表过长的问题...的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。