本文要為大家介紹的命令是 xargs,我們把它稱為護花使者,因為它總是樂於協助其他的命令來完成一些事情。下面一起來看看它是如何護花的。
xargs 是 execute arguments 的縮寫,它的作用是從標准輸入中讀取內容,並將此內容傳遞給它要協助的命令,並作為那個命令的參數來執行。
坊間有一種說法,將 xargs 解讀為乘號(x)和參數(args)的合體,很形象地表達了 xargs 的作用所在。
好了,我們一起來見識一下 xargs 的護花本領吧:
我們用ls命令列出當前路徑下的文件, 包括3個文件
[roc@roclinux ~]$ ls
china.txt usa.txt japan.txt
我們通過xargs + ls的方式列出my.txt文件和your.txt文件
[roc@roclinux ~]$ echo "china.txt usa.txt" | xargs ls
china.txt usa.txt
可以很清晰地看到,xargs 將 echo 的輸出作為自己的標准輸入,並且將其傳遞給了 ls 命令,作為 ls 命令的參數來執行。
xargs 和管道不得不說的故事
為了能夠為大家解釋清楚 xargs 和管道的關系,我們這次選用了更加典型的 cat 命令來為大家舉例。
為什么選擇 cat 命令呢?眾所周知,cat 命令可以接收文件名作為參數,執行后會顯示出文件的內容。但是 cat 命令不能直接從標准輸入接收參數,正如下面的例子:
cat后面直接指定china.txt參數, 可以展示china.txt文件的內容
[roc@roclinux ~]$ cat china.txt
hello beijing
我們嘗試通過標准輸入把參數傳給cat, 結果卻只是顯示了文件名而已
[roc@roclinux ~]$ echo china.txt | cat
china.txt
從上面的例子中,我們可以得出下面的結論:
管道可以實現:將前面的標准輸出作為后面的“標准輸入”。
管道無法實現:將前面的標准輸出作為后面的“命令參數”。
這個時候,就要有請 xargs 這位護花使者了。xargs 所擅長的正是“將標准輸入作為其指定命令的參數”。說着有些拗口,但我相信大家懂的。
xargs果然不負眾望, 協助cat完成了使命
[roc@roclinux ~]$ echo china.txt | xargs cat
hello beijing
我們知道,xargs 會將前一個命令的標准輸出轉換成命令參數,但很多人可能不知道的是,xargs 的標准輸入中出現的“換行符、空格、制表符”都將被空格取代。下面來看一個帶有換行符的例子:
我們准備好了帶有換行的標准輸入
[roc@roclinux ~]$ echo -e "china.txt\njapan.txt"
china.txt
japan.txt
可見, 換行符和空格的作用一樣
[roc@roclinux ~]$ echo -e "china.txt\njapan.txt" | xargs cat
hello beijing
hello tokyo
上面的例子是一個比較簡單的場景,我們有時還會遇到更棘手的情況,一起來看一看。
當命令參數中包含了空格時,情況就會復雜很多,一起來看一個示例。
我們創建了3個日志文件, 且故意讓文件名稱中都含有空格
[roc@roclinux ~]$ for((i=0;i<3;i++)); do touch "test ${i}.log";done
我們列出創建的文件
test 0.log
test 1.log
test 2.log
我們來運行xargs命令, 發現報錯了
[roc@roclinux ~]$ find . -name '*.log' -print | xargs rm
rm: cannot remove ‘./test’: No such file or directory
rm: cannot remove ‘1.log’: No such file or directory
rm: cannot remove ‘./test’: No such file or directory
rm: cannot remove ‘0.log’: No such file or directory
rm: cannot remove ‘./test’: No such file or directory
rm: cannot remove ‘2.log’: No such file or directory
我們在當前目錄中創建了 3 個文件,文件名中間都含有空格。但當 find 命令獲取到的文件名經過 xargs 傳送給 rm 命令時,文件“./test 1.log”就變成了“./test”和“1.log”兩個文件了。即原本 3 個文件名剎那間就變成了 6 個文件名,而這 6 個文件其實並不存在,從而引發了錯誤。
這個錯誤的根源就在於 xargs 默認的分隔符是空格,如果我們能將 xargs 的分隔符改成其他符號,問題就迎刃而解了!
xargs 提供了-0選項,允許將 NULL 作為分隔符,而 find 命令也心有靈犀地提供了對應的選項來產生以 NULL 字符作為分隔符的輸出。
find 命令提供的對應方法是 -print0 選項,在文件名之后輸出 NULL,而不像 -print 選項那樣輸出換行符(換行符會被 xargs 替換成空格)。
正如我們所期待的,命令果然正常執行了,完美!
[roc@roclinux ~]$ find . -name '*.log' -print0 | xargs -0 rm -f
對了,要補充一點,xargs 的-0選項不僅可以將分隔符從默認的空格變成 NULL,還會將單引號、雙引號、反斜線等統統默認為是普通字符。所以說,-0選項特別適合處理命令參數中含有引號、空格、反斜線的情況。
如果你是一位 GEEK,希望掌握得更深入,那么我們就再補充一個知識點。除了可以使用-0選項外,其實還可以使用-d選項來指定任何一個符號作為分隔符。只是我們要格外慎重,避免我們所設定的那個分隔符恰好是我們命令參數中所包含的字符,那就會導致非預期的結果了。
讓護花使者聽我們的話
如果在前一個命令的標准輸出中,會有一些參數是你不希望或者不確定是否要傳送給后面命令的,這個時候我們就希望 xargs 在傳送參數前和我們確認一下。而-p選項恰好可以實現這個願望,我們可以輸入 y 或者 n 來選擇是否要執行當前命令:
[roc@roclinux ~]$ find . -type f |xargs -p rm -f
rm -f ./china.txt ./usa.txt ./japan.txt ?...n
上面的命令中,我們輸入了“n”,也就是不希望刪除這三個文件。不過,這樣的確認粒度太粗,我們希望的是更精細地確認,來試試-n選項吧:
[roc@roclinux 20160408]$ find . -type f |xargs -p -n 1 rm -f
rm -f ./china.txt ?...n
rm -f ./usa.txt ?...y
rm -f ./japan.txt ?...n
使用-n選項,可以讓 xargs 每次只處理 1 個參數,這樣做的好處是避免一次性刪除過多文件,萬一誤刪了不該刪除的文件,那麻煩就大了。
遇到就停止
xargs 已經足夠優秀了,可以幫助我們處理各種各樣的問題。但我們對 xargs 還有更加苛刻的要求,那就是當碰到某個特定的命令參數時,要求 xargs 立即終止並退出。
這個效果,我們可以使用-E選項來實現。
比如,我們正在處理一份日志文件 country.list 中的內容,將日志文件中的字符以空行作為分隔符依次 echo 出來,一旦遇到 korea 便終止退出:
[roc@roclinux ~]$ echo "china usa korea japan" > country.list
[roc@roclinux ~]$ cat country.list
china usa korea japan
[roc@roclinux ~]$ cat country.list | xargs -E 'korea' echo
china usa
可以看到當處理到 korea 的時候,xargs 機警地發現了狀況,於是,按照我們的要求,立即終止並退出了。好乖的 xargs 啊!
你可能一生都不會遇到的參數過長報錯
可能很多運維工程師都沒有遇到過“Argument list too long”這樣的報錯。但要想成為一名資深的運維工程師,了解它的原因和解決方法還是很有必要的。
我們來模擬一個這樣的場景,新建 10 萬個日志文件,並且嘗試用 rm 命令一次性刪除:
[roc@roclinux ~]$ for((i=0;i<100000;i++)); do touch test${i}.log;done
[roc@roclinux ~]$ rm $(find . -type f -name '*.log')
-bash: /bin/rm: Argument list too long
出現了“Argument list too long”報錯,這說明 rm 可接受的參數長度達到了極限。這其實並非 rm 的錯,而是系統限制了參數的長度。通過下面的命令可以查看到系統的參數長度限制值:
[roc@roclinux ~]$ getconf ARG_MAX
2621440
如果真的遇到了參數過長的問題,我們的應對方法其實有很多種,比如把文件手工分組以縮短參數的長度,但是這個方法並不優雅,而且費時費力。最優雅的方法當然就是借助 xargs 啦:
[roc@roclinux ~]$ find . -name '*.log' -print | xargs rm
借助 xargs,並利用管道的特性,find 命令將輸出的內容分段傳給 rm 命令,而不是一股腦兒地塞過去。這樣一來,rm 命令可以先處理最先獲取的一部分文件,然后再處理下一批,並一直繼續下去,直到全部刪除為止。
好了,xargs 這位護花使者是不是既聰明又聽話呢,在運維過程中,嘗試使用起來吧,你會事半功倍的!