xargs命令_Linux xargs命令:一個給其他命令傳遞引數的過濾器

2020-07-16 10:04:29
本文要為大家介紹的命令是 xargs,我們把它稱為護花使者,因為它總是樂於協助其他的命令來完成一些事情。下面一起來看看它是如何護花的。

xargs 是 execute arguments 的縮寫,它的作用是從標準輸入中讀取內容,並將此內容傳遞給它要協助的命令,並作為那個命令的引數來執行。

坊間有一種說法,將 xargs 解讀為乘號(x)和引數(args)的合體,很形象地表達了 xargs 的作用所在。

好了,我們一起來見識一下 xargs 的護花本領吧:
#我們用ls命令列出當前路徑下的檔案, 包括3個檔案
[[email protected] ~]$ ls
china.txt  usa.txt japan.txt
 
#我們通過xargs + ls的方式列出my.txt檔案和your.txt檔案
[[email protected] ~]$ 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檔案的內容
[[email protected] ~]$ cat china.txt
hello beijing
 
#我們嘗試通過標準輸入把引數傳給cat, 結果卻只是顯示了檔名而已
[[email protected] ~]$ echo china.txt | cat
china.txt

從上面的例子中,我們可以得出下面的結論:
  • 管道可以實現:將前面的標準輸出作為後面的“標準輸入”。
  • 管道無法實現:將前面的標準輸出作為後面的“命令引數”。

這個時候,就要有請 xargs 這位護花使者了。xargs 所擅長的正是“將標準輸入作為其指定命令的引數”。說著有些拗口,但我相信大家懂的。
#xargs果然不負眾望, 協助cat完成了使命
[[email protected] ~]$ echo china.txt | xargs cat
hello beijing

我們知道,xargs 會將前一個命令的標準輸出轉換成命令引數,但很多人可能不知道的是,xargs 的標準輸入中出現的“換行符、空格、製表符”都將被空格取代。下面來看一個帶有換行符的例子:
#我們準備好了帶有換行的標準輸入
[[email protected] ~]$ echo -e "china.txtnjapan.txt"
china.txt
japan.txt
 
#可見, 換行符和空格的作用一樣
[[email protected] ~]$ echo -e "china.txtnjapan.txt" | xargs cat
hello beijing
hello tokyo

上面的例子是一個比較簡單的場景,我們有時還會遇到更棘手的情況,一起來看一看。

當命令引數中包含了空格時,情況就會複雜很多,一起來看一個範例。
#我們建立了3個紀錄檔檔案, 且故意讓檔名稱中都含有空格
[[email protected] ~]$ for((i=0;i<3;i++)); do touch "test ${i}.log";done
 
#我們列出建立的檔案
[[email protected] ~]$ ls -1F
test 0.log
test 1.log
test 2.log
 
#我們來執行xargs命令, 發現報錯了
[[email protected] ~]$  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 替換成空格)。

正如我們所期待的,命令果然正常執行了,完美!
[[email protected] ~]$ find . -name '*.log' -print0 | xargs -0 rm -f

對了,要補充一點,xargs 的-0選項不僅可以將分隔符從預設的空格變成 NULL,還會將單引號、雙引號、反斜線等統統預設為是普通字元。所以說,-0選項特別適合處理命令引數中含有引號、空格、反斜線的情況。

如果你是一位 GEEK,希望掌握得更深入,那麼我們就再補充一個知識點。除了可以使用-0選項外,其實還可以使用-d選項來指定任何一個符號作為分隔符。只是我們要格外慎重,避免我們所設定的那個分隔符恰好是我們命令引數中所包含的字元,那就會導致非預期的結果了。

讓護花使者聽我們的話

如果在前一個命令的標準輸出中,會有一些引數是你不希望或者不確定是否要傳送給後面命令的,這個時候我們就希望 xargs 在傳送引數前和我們確認一下。而-p選項恰好可以實現這個願望,我們可以輸入 y 或者 n 來選擇是否要執行當前命令:
[[email protected] ~]$ find . -type f |xargs -p rm -f
rm -f ./china.txt ./usa.txt ./japan.txt ?...n

上面的命令中,我們輸入了“n”,也就是不希望刪除這三個檔案。不過,這樣的確認粒度太粗,我們希望的是更精細地確認,來試試-n選項吧:
[[email protected] 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 便終止退出:
[[email protected] ~]$ echo "china usa korea japan" > country.list
 
[[email protected] ~]$ cat country.list
china usa korea japan
 
[[email protected] ~]$ cat country.list | xargs -E 'korea' echo
china usa

可以看到當處理到 korea 的時候,xargs 機警地發現了狀況,於是,按照我們的要求,立即終止並退出了。好乖的 xargs 啊!

你可能一生都不會遇到的引數過長報錯

可能很多運維工程師都沒有遇到過“Argument list too long”這樣的報錯。但要想成為一名資深的運維工程師,了解它的原因和解決方法還是很有必要的。

我們來模擬一個這樣的場景,新建 10 萬個紀錄檔檔案,並且嘗試用 rm 命令一次性刪除:
[[email protected] ~]$ for((i=0;i<100000;i++)); do touch test${i}.log;done
 
[[email protected] ~]$ rm $(find . -type f -name '*.log')
-bash: /bin/rm: Argument list too long

出現了“Argument list too long”報錯,這說明 rm 可接受的引數長度達到了極限。這其實並非 rm 的錯,而是系統限制了引數的長度。通過下面的命令可以檢視到系統的引數長度限制值:
[[email protected] ~]$ getconf ARG_MAX
2621440

如果真的遇到了引數過長的問題,我們的應對方法其實有很多種,比如把檔案手工分組以縮短引數的長度,但是這個方法並不優雅,而且費時費力。最優雅的方法當然就是借助 xargs 啦:
[[email protected] ~]$ find . -name '*.log' -print | xargs rm

借助 xargs,並利用管道的特性,find 命令將輸出的內容分段傳給 rm 命令,而不是一股腦兒地塞過去。這樣一來,rm 命令可以先處理最先獲取的一部分檔案,然後再處理下一批,並一直繼續下去,直到全部刪除為止。

好了,xargs 這位護花使者是不是既聰明又聽話呢,在運維過程中,嘗試使用起來吧,你會事半功倍的!