為什么需要xargs
管道實現的是將前面的stdout作為后面的stdin,但是有些命令不接受管道的傳遞方式,最常見的就是ls命令。有些時候命令希望管道傳遞的是參數,但是直接用管道有時無法傳遞到命令的參數位,這時候需要xargs,xargs實現的是將管道傳輸過來的stdin進行處理然后傳遞到命令的參數位上。也就是說xargs完成了兩個行為:處理管道傳輸過來的stdin;將處理后的傳遞到正確的位置上。
可以試試運行下面的幾條命令,應該能很好理解xargs的作用了:
[root@node2 scprits]# echo "/etc/inittab" | cat /etc/inittab [root@node2 scprits]# echo "/etc/inittab" | xargs cat # inittab is no longer used when using systemd. # # ADDING CONFIGURATION HERE WILL HAVE NO EFFECT ON YOUR SYSTEM. # # Ctrl-Alt-Delete is handled by /usr/lib/systemd/system/ctrl-alt-del.target # # systemd uses 'targets' instead of runlevels. By default, there are two main targets: # # multi-user.target: analogous to runlevel 3 # graphical.target: analogous to runlevel 5 # # To view current default target, run: # systemctl get-default # # To set a default target, run: # systemctl set-default TARGET.target #
xargs的作用不僅僅限於簡單的stdin傳遞到命令的參數位,它還可以將stdin或者文件stdin分割成批,每個批中有很多分割片段,然后將這些片段按批交給xargs后面的命令進行處理。
通俗的講就是原來只能一個一個傳遞,分批可以實現10個10個傳遞,每傳遞一次,xargs后面的命令處理這10個中的每一個,處理完了處理下一個傳遞過來的批,如下圖。
但是應該注意的是,盡管實現了分批處理,但是默認情況下並沒有提高任何效率,因為分批傳遞之后還是一次執行一個。而且有時候分批傳遞后是作為一個參數的整體,並不會將分批中的信息分段執行。這樣看來,實現分批傳遞的目的僅僅是為了解決一些問題。但事實上,xargs提供了"-P"選項,用於指定並行執行的數量(默認只有一個處理進程,不會提升效率,可以指定為N個子進程,或者指定為0表示盡可能多地利用CPU),這樣就能讓分批操作更好地利用多核cpu,從而提升效率。例如上面分成了兩批,指定"-P 2"可以並發執行這兩個批,而非執行完第一批再執行第二批。關於並行處理的詳細內容,見后文:高速並行處理之:xargs -P。
剩下的就是處理xargs的細節問題了,比如如何分割(xargs、xargs -d、xargs -0),分割后如何划批(xargs -n、xargs -L),參數如何傳遞(xargs -i)。另外xargs還提供詢問交互式處理(-p選項)和預先打印一遍命令的執行情況(-t選項),傳遞終止符(-E選項)等。
其實這里已經暗示了xargs處理的優先級或順序了:先分割,再分批,然后傳遞到參數位。
分割有三種方法:獨立的xargs、xargs -d和xargs -0。后兩者可以配合起來使用,之所以不能配合獨立的xargs使用,答案是顯然的,指定了-d或-0選項意味着它不再是獨立的。
分批方法從邏輯上說是兩種:-n選項和-L選項。但我覺得還應該包含傳遞階段的選項-i。假如-i不是分批選項,則它將接收分批的結果。然而事實並非如此,當-i選項指定在-n和-L選項之后,會覆蓋-n或-L。后文中我將其當成分批選項來介紹和說明。
當然上述只是一個概括,更具體的還要看具體的選項介紹,而且很可能一個xargs中用不到這么多選項,但是理解這個很重要,否則在分割分批和傳遞上很容易出現疑惑。
文本意義上的符號和標記意義上的符號
在解釋xargs和它的各種選項之前,我想先介紹一個貫穿xargs命令的符號分類:文本意義上的空格、制表符、反斜線、引號和非文本意義上的符號。我覺得理解它們是理解xargs分割和分批原理的關鍵。
文本意義上的空格、制表符、反斜線、引號:未經處理就已經存在的符號,例如文本的內容中出現這些符號以及在文件名上出現了這些符號都是文本意義上的。與之相對的是非文本意義的符號,由於在網上沒找到類似的文章和解釋,所以我個人稱之為標記意義上的符號:處理后出現的符號,例如ls命令的結果中每個文件之間的制表符,它原本是不存在的,只是ls命令處理后的顯示方式。還包括每個命令結果的最后的換行符,文件內容的最后一行結尾的換行符。
如下圖,屬於標記意義上的符號都用紅色圓圈標記出來了。
其實它們的關系有點類似於字面意義的符號和特殊符號之間的關系,就像有時候特殊符號需要進行轉義才能表示為普通符號。
因為翻了百度、谷歌和一些書都沒說這些方面的分類。但文本和非文本的符號在xargs分割的時候確實是區別對待的,所以我覺得有必要給個稱呼好引用並說明它們,也就是說以上稱呼完全是我個人的稱呼。
分割行為之:xargs
[root@node2 scprits]# cd /tmp [root@node2 tmp]# rm -fr * [root@node2 tmp]# ls [root@node2 tmp]# mkdir a b c d test logdir shdir [root@node2 tmp]# touch "one space.log" [root@node2 tmp]# touch logdir/{1..10}.log [root@node2 tmp]# touch shdir/{1..5}.sh [root@node2 tmp]# echo "the second sh the second line" > shdir/2.sh [root@node2 tmp]# cat <<eof>shdir/1.sh > > the first sh > the second line > eof
對於xargs,它將接收到的stdout處理后傳遞到xargs后面的命令參數位,不寫命令時默認的命令是echo。
[root@node2 tmp]# cat shdir/1.sh | xargs the first sh the second line [root@node2 tmp]# cat shdir/1.sh | xargs echo the first sh the second line
將分行處理掉不是echo實現的,而是管道傳遞過來的stdin經過xargs處理后的:將所有空格、制表符和分行符都替換為空格並壓縮到一行上顯示,這一整行將作為一個整體,這個整體的所有空格屬性繼承xargs處理前的符號屬性,即原來是文本意義的或標記意義的在替換為空格后符號屬性不變。這個整體可能直接交給命令或者作為stdout通過管道傳遞給管道右邊的命令,這時結果將作為整體傳遞,也可能被xargs同時指定的分批選項分批處理。
如果想要保存制表符、空格等特殊符號,需要將它們用單引號或雙引號包圍起來,但是單雙引號(和反斜線)都會被xargs去掉。
另外經過我的測試,單引號和雙引號的存在讓處理變的很不受控制,經常會影響正常的分割和處理。
如果不指定分批選項,xargs的一整行結果將作為一個整體輸出,而不是分隔開的。也許看處理的結果感覺是分開處理的,例如下面的第一個命令,但是這是因為ls允許接受多個空格分開的參數,執行第二個命令,可以證明它確實是將整行作為整體傳輸給命令的。
[root@node2 tmp]# find /tmp -maxdepth 1 | xargs ls /tmp/one_space.log /tmp: a b c d logdir one_space.log shdir test /tmp/a: /tmp/b: /tmp/c: /tmp/d: /tmp/.font-unix: /tmp/.ICE-unix: /tmp/logdir: 10.log 1.log 2.log 3.log 4.log 5.log 6.log 7.log 8.log 9.log /tmp/shdir: 1.sh 2.sh 3.sh 4.sh 5.sh /tmp/test: /tmp/.Test-unix: /tmp/.X11-unix: /tmp/.XIM-unix:
[root@node2 tmp]# find /tmp -maxdepth 1 | xargs -p ls ls /tmp /tmp/.ICE-unix /tmp/.Test-unix /tmp/.font-unix /tmp/.XIM-unix /tmp/.X11-unix /tmp/a /tmp/b /tmp/c /tmp/d /tmp/test /tmp/logdir /tmp/shdir /tmp/one_space.log ?...
如果對獨立的xargs指定分批選項,則有兩種分批可能:指定-n時按空格分段,然后划批,不管是文本意義的空格還是標記意義的空格,只要是空格都是-n的操作對象;指定-L或者-i時按段划批,文本意義的符號不被處理。
[root@node2 tmp]# ls a b c d logdir one_space.log shdir test [root@node2 tmp]# ls | xargs -n 2 -n是按空格分割的 a b c d logdir one_space.log shdir test
[root@node2 tmp]# ls | xargs -L 2 a b c d logdir one_space.log shdir test 文件名中的空格沒有分割這個段
[root@node2 tmp]# ls | xargs -i -p echo {} echo a ?... echo b ?... echo c ?... echo d ?... echo logdir ?... echo one_space.log ?... echo shdir ?... echo test ?...
使用xargs -p或xargs -t觀察命令的執行過程
使用-p選項是交互詢問式的,只有每次詢問的時候輸入y(或yes)才會執行,直接按enter鍵是不會執行的。
使用-t選項是在每次執行xargs后面的命令都會先在stderr上打印一遍命令的執行過程然后才正式執行。
使用-p或-t選項就可以根據xargs后命令的執行順序進行推測,xargs是如何分段、分批以及如何傳遞的,這通過它們有助於理解xargs的各種選項。
[root@node2 tmp]# ls | xargs -n 2 -t echo a b 先打印一次命令,表示這一次只echo兩個參數:a和b a b echo c d 表示這次只打印c和d
c d
c d echo logdir one_space.log logdir one_space.log echo shdir test shdir test
[root@node2 tmp]# ls | xargs -n 2 -p echo a b ?...y a b echo c d ?...y c d echo logdir one_space.log ?...y logdir one_space.log echo shdir test ?...y shdir test
從上面的-t和-p的結果上都可以知道每次傳遞兩個參數。
分割行為之:xargs -d
xargs -d有如下行為:
? xargs -d可以指定分段符,可以是單個符號、字母或數字。如指定字母o為分隔符:xargs -d"o"。
? xargs -d是分割階段的選項,所以它優先於分批選項(-n、-L、-i)。
? xargs -d不是先xargs再-d處理的,它是區別於獨立的xargs的另一個分割選項。
xargs -d整體執行有幾個階段:
? 替換:將接收stdin的所有的標記意義的符號替換為\n,替換完成后所有的符號(空格、制表符、分行符)變成字面意義上的普通符號,即文本意義的符號。
? 分段:根據-d指定的分隔符進行分段並用空格分開每段,由於分段前所有符號都是普通字面意義上的符號,所以有的分段中可能包含了空格、制表符、分行符。也就是說除了-d導致的分段空格,其余所有的符號都是分段中的一部分。
? 輸出:最后根據指定的分批選項來輸出。這里需要注意,分段前后有特殊符號時會完全按照符號輸出。
從上面的階段得出以下兩結論:
(1)xargs -d會忽略文本意義上的符號。對於文本意義上的空格、制表符、分行符,除非是-d指定的符號,否則它們從來不會被處理,它們一直都是每個分段里的一部分;
(2)由於第一階段標記意義的符號會替換為分行符號,所以傳入的stdin的每個標記意義符號位都在最終的xargs -d結果上分行了,但是它們已經是分段中的普通符號了,除非它們是-d指定的符號。
例如對ls的結果指定"o"為分隔符。
[root@node2 tmp]# ls a b c d logdir one_space.log shdir test [root@node2 tmp]# ls | xargs -d"o" a b c d l gdir ne_space.l g shdir test #指定字母"o"為分隔符
如果使用xargs -d時不指定分批選項,則整個結果將作為整體輸出。
[root@node2 tmp]# ls | xargs -d"o" -p echo a b c d l gdir ne_space.l g shdir test ?...y a b c d l gdir ne_space.l g shdir test
如果指定了分批選項,則按照-d指定的分隔符分段后的段分批,這時使用-n、-L或-i的結果是一樣的。例如使用-n選項來觀察是如何分批的。
[root@node2 tmp]# ls | xargs -d"o" -n 2 -t echo a b c d l gdir 每兩段是一個批。 a b c d l gdir # 注意這里有個空行。是因為段的分隔符處於下一段的行開頭,它的前面有個\n符號會按符號輸出。 echo ne_space.l g shdir test ne_space.l g shdir test
[root@node2 tmp]# [root@xuexi tmp]# ls | xargs -d"o" -n 2 -bash: [root@xuexi: 未找到命令 [root@node2 tmp]# ls | xargs -d"o" -n 2 a b c d l gdir ne_space.l g shdir test
再看看-n 1的輸出。
[root@node2 tmp]# ls | xargs -d"o" -n 1 a b c d l gdir ne_space.l g shdir test
分割行為之:xargs -0
xargs -0的行為和xargs -d基本是一樣的,只是-d是指定分隔符,-0是指定固定的\0作為分隔符。其實xargs -0就是特殊的xargs -d的一種,它等價於xargs -d"\0"。
xargs -0行為如下:
? xargs -0是分割階段的選項,所以它優先於分批選項(-n、-L、-i)。
? xargs -0不是先xargs再-0處理的,它是區別於獨立的xargs的另一個分割選項。
? xargs -0可以處理接收的stdin中的null字符(\0)。如果不使用 -0選項或- -null選項,檢測到\0后會給出警告提醒,並只向命令傳遞非\0段。xargs -0和- -null是一樣的效果。
xargs -0整體執行有幾個階段:
? 替換:將接收stdin的所有的標記意義的符號替換為\n,替換完成后所有的符號(空格、制表符、分行符)變成字面意義上的普通符號,即文本意義的符號。
? 分段:將檢測到的null字符(\0)使用標記意義上的空格來分段,由於分段前所有符號都是普通字面意義上的符號,所以有的分段中可能包含了空格、制表符、分行符。也就是說除了-0導致的分段空格,其余所有的符號都是分段中的一部分。
如果沒有檢測到\0,則接收的整個stdin將成為一個不可分割的整體,任何分批選項都不會將其分割開,因為它只有一個段。
? 輸出:最后根據指定的分批選項來輸出。這里需要注意,分段前后有特殊符號時會完全按照符號輸出。
根據上面的結論可知,xargs -0會忽略所有文本意義上的符號,它的主要目的是處理\0符號。
[root@node2 tmp]# touch "one space.log"
[root@node2 tmp]# ls | tr " " "\t" | xargs -0 #忽略文本意義上的制表符
a
b
c
d
logdir
one space.log
one_space.log
shdir
test
[root@node2 tmp]# ls | tr " " " " | xargs -0 a b c d logdir one space.log one_space.log shdir test
如果檢測到\0而沒有使用-0或--null處理則給出警告。注意警告后執行哪些文件。
[root@node2 tmp]# ls | tr " " "\0" a b c d logdir onespace.log one_space.log shdir test
[root@node2 tmp]# ls | tr " " "\0" | xargs xargs: 警告: 輸入中有個 NUL 字符。它不能在參數列表中傳送。您是想用 --null 選項嗎? a b c d logdir one one_space.log shdir test
再例如,將所有的換行符換成null字符,結果中除了最前面的字母a和由於空格而不被\0影響的space.log,其余的由於全部有\0全部被忽略。
[root@node2 tmp]# ls | tr "\n" "\0" abcdlogdirone space.logone_space.logshdirtest[root@node2 tmp]# 只有a的前面和space.log的前面是沒有\0的
[root@node2 tmp]# ls | tr "\n" "\0" | xargs xargs: 警告: 輸入中有個 NUL 字符。它不能在參數列表中傳送。您是想用 --null 選項嗎? a space.log
使用-0或--null來解決問題,也可以使用等價的xargs -d"\0"來解決。
[root@node2 tmp]# ls | tr "\n" "\0" | xargs -0 a b c d logdir one space.log one_space.log shdir test [root@node2 tmp]# ls | tr "\n" "\0" | xargs -d"\0" a b c d logdir one space.log one_space.log shdir test
如果使用xargs -0時不指定分批選項(-n、-L、-i),則處理后的結果將作為一個整體輸出。
如果指定了分批選項,並且檢測到了null字符,則以\0位的空格分段划批,這時使用-n、-L或-i的結果是一樣的。例如使用-n選項來觀察是如何分批的。
[root@node2 tmp]# ls | tr "\n" "\0" | xargs -0 -n 3 a b c d logdir one space.log one_space.log shdir test
如果指定了分批選項,但沒有檢測到null字符,則整個結果將稱為一個不可分割整體,這時使用分批選項是完全無意義的。
[root@node2 tmp]# ls | xargs -0 -n 3 -p echo a b c d logdir one space.log one_space.log shdir test ?...y a b c d logdir one space.log one_space.log shdir test
分批行為
分批用於指定每次傳遞多少個分段。有三種分批選項:-n,-L和-i。在本文的開頭已經指明了為什么-i是分批選項,但是這里還是要介紹它邏輯上定義的功能:參數替換。
既然三種選項都是分批選項,如果在一個xargs中使用了多個分批選項,則它們之間必然會沖突,它們的規則是寫在后面的生效,前面的分批選項被忽略。
xargs -n
xargs -n分兩種情況:和獨立的xargs一起使用,這時按照每個空格分段划批;和xargs -d或xargs -0一起使用,這時按段分批,即不以空格、制表符和分行符分段划批。
[root@node2 tmp]# ls | xargs -n 3 -p echo a b c ?...y a b c echo d logdir one ?...y d logdir one echo space.log one_space.log shdir ?...y space.log one_space.log shdir echo test ?...y test
和獨立的xargs一起使用,以空格分段划批
[root@node2 tmp]# ls | xargs -d"o" -n 3 -p echo a b c d l gdir ne space.l ?...y a b c d l gdir ne space.l echo g ne_space.l g shdir test ?...y g ne_space.l g shdir test
# 和xargs -d一起使用,按段分批
xargs -L
和-n選項類似,唯一的區別是-L永遠是按段划批,而-n在和獨立的xargs一起使用時是按空格分段划批的。
該選項的一個同義詞是-l,但是man推薦使用-L替代-l,因為-L符合POSIX標准,而-l不符合。使用--max-lines也可以。
也許你man xargs時發現-L選項是指定傳遞時最大傳遞行數量的,man的結果如下圖。但是通過下面的實驗可以驗證其實-L是指定傳遞的最大段數,也就是分批。
[root@node2 tmp]# ls | xargs -L 3 -p echo a b c ?...y a b c echo d logdir one space.log ?...y d logdir one space.log echo one_space.log shdir test ?...y one_space.log shdir test
這里可以證明-L和-n的區別
[root@node2 tmp]# ls | xargs -d"o" -L 3 -p echo a b c d l gdir ne space.l ?...y a b c d l gdir ne space.l echo g ne_space.l g shdir test ?...y g ne_space.l g shdir test
xargs -i和xargs -I
xargs -i選項在邏輯上用於接收傳遞的分批結果。
如果不使用-i,則默認是將分割后處理后的結果整體傳遞到命令的最尾部。但是有時候需要傳遞到多個位置,不使用-i就不知道傳遞到哪個位置了,例如重命名備份的時候在每個傳遞過來的文件名加上后綴.bak,這需要兩個參數位。
使用xargs -i時以大括號{}作為替換符號,傳遞的時候看到{}就將被結果替換。可以將{}放在任意需要傳遞的參數位上,如果多個地方使用{}就實現了多個傳遞。
xargs -I(大寫字母i)和xargs -i是一樣的,只是-i默認使用大括號作為替換符號,-I則可以指定其他的符號、字母、數字作為替換符號,但是必須用引號包起來。man推薦使用-I代替-i,但是一般都使用-i圖個簡單,除非在命令中不能使用大括號,如touch {1..1000}.log時大括號就不能用來做替換符號。
例如下面的重命名備份過程。
[root@node2 tmp]# ls logdir/ 10.log 1.log 2.log 3.log 4.log 5.log 6.log 7.log 8.log 9.log [root@node2 tmp]# ls logdir/ | xargs -i mv ./logdir/{} ./logdir/{}.bak [root@node2 tmp]# ls logdir/ 10.log.bak 1.log.bak 2.log.bak 3.log.bak 4.log.bak 5.log.bak 6.log.bak 7.log.bak 8.log.bak 9.log.bak
但是我將“-i”選項划分在分批選項里,它默認一個段為一個批,每次傳遞一個批也就是傳遞一個段到指定的大括號{}位上。在稍后的分批選項的生效規則部分我會給出我的理由。
由於-i選項是按分段來傳遞的。所以盡管看上去等價的xargs echo和xargs -i echo {}並不等價。
[root@node2 tmp]# ls | xargs echo a b c d logdir one space.log one_space.log shdir test
[root@node2 tmp]# ls | xargs -i echo {} a b c d logdir one space.log one_space.log shdir test
既然使用-i后是分段傳遞的,這就意味着指定了它就無法實現按批傳遞多個參數了;並且如果使用多個大括號,意味着必須使用-i,那么也無法分批傳遞。
例如,想將數字1-10每3個數顯示在start和end之間。效果如下:
start 1 2 3 end start 4 5 6 end start 7 8 9 end start 10 end
由於指定了參數傳遞位置,所以必須使用-i,那么就無法一次傳遞3個數。要解決這個問題,就要想辦法讓每三個數分一次段然后使用-i傳遞,方法也就隨之而來了。可以將每三個數分一次行寫入一個文件。如:
[root@node2 tmp]# cat <<eof>logdir/1.log > 1 2 3 > 4 5 6 > 7 8 9 > 10 > eof
再使用xargs -i分批傳遞。
[root@node2 tmp]# cat logdir/1.log | xargs -i echo "start {} end" start 1 2 3 end start 4 5 6 end start 7 8 9 end start 10 end
也可以使用多次xargs。很多時候無法解決分段的問題都可以通過多次使用xargs來解決。
[root@node2 tmp]# echo {1..10} | xargs -n 3 | xargs -i echo "start {} end" start 1 2 3 end start 4 5 6 end start 7 8 9 end start 10 end
分批選項的生效規則
-i、-L、-n選項都是分批選項。它們的生效規則是,誰指定在后面,誰就生效。
下面給出證明。
下面-i放在-n、-L之后,結果是-n、-L被忽略。
[root@node2 tmp]# ls | xargs -d"o" -n 2 -p -i echo {} echo a b c d l ?...y a b c d l echo gdir ?...y gdir echo ne space.l ?...y ne space.l echo g ?...y g echo ne_space.l ?...y ne_space.l echo g shdir test ?...y g shdir test
[root@node2 tmp]# ls | xargs -d"o" -L 3 -i -p echo {} echo a b c d l ?...y a b c d l echo gdir ?...y gdir echo ne space.l ?...y ne space.l echo g ?...y g echo ne_space.l ?...y ne_space.l echo g shdir test ?...y g shdir test
和上面的一樣,
說明-L選項被忽略
下面是-L放在-n后,結果是-n被忽略。
[root@node2 tmp]# ls | xargs -d"o" -n 2 -p -L 1 echo echo a b c d l ?...y a b c d l echo gdir ?...y gdir echo ne space.l ?...y ne space.l echo g ?...y g echo ne_space.l ?...y ne_space.l echo g shdir test ?...y g shdir test
結果也是一段一段輸出的,說明-n選項被忽略
根據上面的證明,其實也就給出了我認為-i選項是分批選項的理由,因為它覆蓋了-n和-L,實際上在新的man xargs中已經給出了解釋,它隱含"-L 1"。其實如果說-i包含分批並傳遞這兩個作用更嚴格一點。
分批選項的一個典型應用
分批選項有時特別有用,例如腳本規定每次只能傳輸三個參數。有時候rm -rf的文件數量特別多的時候會提示參數列表太長而導致失敗,這時就可以分批來按批刪除,不僅rm -rf,其他很多本身就可以實現批量操作的命令都有可能出現這種參數列表過長的錯誤,如touch {1..10000000}也會提示錯誤。
假設目前在/tmp/longshuai/下有29W個.log文件,如果直接刪除將會提示參數列表過長。
[root@xuexi tmp]# rm -fr /tmp/longshuai/*.log
-bash: /bin/rm: Argument list too long
這時如果使用xargs就可以分批丟給rm -fr處理了。下面一批10000個,刪除29批。
[root@xuexi tmp]# cd /tmp/longshuai/ && ls | xargs -n 10000 rm -rf
如果不使用分批直接交給rm -rf也是一樣可以執行成功的。如果想知道為什么可以請看后文xargs -s。
[root@xuexi tmp]# cd /tmp/longshuai/ && ls | xargs rm -rf
這里說下如何統計某個目錄下的文件數量?ll后使用"-"開頭來過濾出文件,然后使用wc統計行數。
[root@xuexi tmp]# ll /tmp/longshuai/ | grep "^-" | wc -l
終止行為之:xargs -E
指定終止符號,搜索到了指定的終止符就完全退出傳遞,命令也就到此結束。
-e選項也是,但是官方建議使用-E替代-e,因為-E是POSIX標准兼容的,而-e不是。
-E會將結果空格、制表符、分行符替換為空格並壓縮到一行上顯示。
據我測試,-E似乎只能和獨立的xargs使用,和-0、-d配合使用時都會失效。那么稍后我就只測試和獨立的xargs配合使用的情況了。
-E優先於-n、-L和-i執行。如果是分批選項先執行,則下面的第二個結果將壓縮在一行上。
指定的終止符必須是完整的,例如想在遇到“xyz.txt”的符號終止時,只能指定完整的xyz.txt符號,不能指定.txt或者txt這樣的符號。如何判斷指定的終止符號是否完整,就-E與獨立的xargs配合的情況而言分兩種情況:如果沒指定分批選項或者指定的分批選項是-n或者-L時,以空格為分割符,兩個空格之間的段都是完整的;如果指定的分批選項是-i,則以段為分割符。
例如,下面的示例。觀察實驗結果中的one space.log分割的情況。
[root@xuexi tmp]# ls
a b c d logdir one space.log shdir sh.txt test vmware-root x.txt
[root@xuexi tmp]# ls | xargs -E one #不指定分批選項
a b c d logdir
[root@xuexi tmp]# ls | xargs -n 2 -E one #指定-n,one后面的所有的都終止傳遞
a b
c d
logdir
[root@xuexi tmp]# ls | xargs -L 2 -E"one" #同-n 選項
a b
c d
logdir
[root@xuexi tmp]# ls | xargs -i -E"one space.log" echo {} #和-i配合使用時指定完整的段才可以
a
b
c
d
logdir
[root@xuexi tmp]# ls | xargs -i -E"one" -p echo {} #非完整段終止失效
echo a ?...
echo b ?...
echo c ?...
echo d ?...
echo logdir ?...
echo one space.log ?...
echo shdir ?...
echo sh.txt ?...
echo test ?...
echo vmware-root ?...
echo x.txt ?...
xargs的處理總結
總結只有一張表。算是用來復習前面所述。
分割行為 | 特殊符號處理方式 | 分段方法 | 配合分批選項 | 分批方法 |
xargs | 空格、制表符、分行符替換為空格,引號和反斜線刪除。處理完后只有空格。如果空格、制表符和分行符使用引號包圍則可以保留 | 結果繼承處理前的符號性質(文本符號還是標記意義符號)。 | -n | 以分段結果中的每個空格分段,進而分批。不管是文本還是標記意義的空格,只要是空格 |
-L、-i | 以標記意義上的空格分段,進而分批 | |||
不指定 | 結果作為整體輸出 | |||
xargs -d | xargs -d 不處理文本意義上的符號,所有標記意義上的符號替換為換行符\n,將-d指定的分割符替換為標記意義上的空格。 結果中除了最后的空行和-d指定的分割符位的分段空格,其余全是文本意義上的符號 |
按照-d指定的符號進行分段,每個段中可能包含文本意義上的空格、制表符、甚至是分行符。 | -n、-L、-i | 以標記意義上的符號(即最后的空行和-d指定分隔符位的空格)分段,進而分批。分段結果中保留所有段中的符號,包括制表符和分行符。 |
不指定 | 結果作為整體輸出 | |||
xargs -0 | 不處理文本意義上的符號,將非\0的標記意義上的符號替換為\n,將\0替換為空格。 結果中除了最后空行和\0位的空格,其余都是文本意義上的符號 |
以替換\0位的空格分段,每個段中可能包含文本意義上的空格、制表符、甚至是分行符。 如果沒檢測到\0,則只有一個不可分割的段。 |
-n、-L、-i | 檢測到\0時,以標記意義上的符號(即最后的空行和\0位的空格)分段,進而分批。分段結果中保留所有段中的符號,包括制表符和分行符。 |
未檢測到\0時,整個結果作為不可分割整體,使用分批選項是無意義的 | ||||
不指定 | 結果作為整體輸出 |
xargs與find的結合
xargs和find同屬於一個rpm包findutils,xargs原本就是為find而開發的,它們之間的配合應當是天衣無縫的。
一般情況下它們隨意結合都無所謂,按正常方式進行即可。但是當刪除文件時,特別需要將文件名含有空白字符的文件納入考慮。
[root@node2 tmp]# touch one;touch space.log [root@node2 tmp]# ls a b c d logdir one one space.log one_space.log shdir space.log test [root@node2 tmp]#
現在假設通過find搜索到了one space.log。
[root@node2 tmp]# find -name "* *.log" ./one space.log
如果直接交給xargs rm -rf,由於xargs處理后不指定分批選項時以空格分段,所以改名了的行為將是rm -rf ./one space.log,這表示要刪除的是當前目錄下的one和當前目錄下的space.log,而不是one space.log。
有多種方法可以解決這個問題。思路是讓找到的“one space.log”成為一個段,而不是兩個段。我給出了常見的兩種。
方法一:通過常用的find的-print0選項使用\0來分隔而不是\n分隔,再通過xargs -0來配對保證one space.log的整體性。因為-print0后one space.log的前后各有一個\0,但是文件名中間沒有。
[root@node2 tmp]# find -name "* *.log" -print0 | xargs -0 rm -rf [root@node2 tmp]# ls a b c d logdir one one_space.log shdir space.log test
當然,能使用-0肯定也能使用-d了。
[root@node2 tmp]# find -name "* *.log" -print0 | xargs -d "x" rm -rf #隨意指定非文件名中的字符都行,不一定非要\0
方法二:不在find上處理,在xargs上處理,只要通過配合-i選項,就能宣告它的整體性。
[root@node2 tmp]# find -name "* *.log" | xargs -i rm -rf "{}" [root@node2 tmp]# ls a b c d logdir one one_space.log shdir space.log test
相較而言,方法一使用的更廣泛更為人所知,但是方法二更具有通用性,對於非find如ls命令也可以進行處理。
還可以使用tr將find的換行符換成其他符號再xargs分割配對也行。
除了find -print0可以輸出\0字符,Linux中還有其他幾個命令配合參數也可以實現:locate -0,grep -z或grep -Z,sort -z等。
xargs -s之為什么ls | xargs rm -rf能執行成功?
使用下面的示例配合圖來解釋。
[root@node2 tmp]# cd logdir [root@node2 logdir]# touch {1..1000000}
-bash: /usr/bin/touch: 參數列表過長
[root@node2 logdir]# echo {1..1000000} | xargs touch
#執行的時候記得使用-p選項,否則慢慢等吧。
問題一:正常創建批量文件touch {1..1000000}是無法執行成功的,會提示參數列表過長。但是上面的最后一個命令為什么能執行成功?
問題二:xargs處理后如果不指定-n選項,那么它是整體傳遞的,如果這個整體非常非常大,如上面的100W個參數,按理說touch也是無法成功的。為什么成功了?
xargs有一個默認的選項-s,它指定每次傳遞的最大字節數,如果不顯式指定-s,系統默認是128KB。也就是說如果一次傳遞的參數很多很大,那么將由系統自動分割為每128KB傳遞一次。這就是上面的命令能執行成功的原因。
上面的100W個參數,以差不多每個參數5個數字位加一個分段位空格共6個字節計算,128K有128*1024/6=21845個數字,這和我使用-p測試的詢問位置是接近的,如下圖,由於前10000個數字少於5個字節,所以比21845多一點。第二次停止的位置是45539,45539-23695=21844,這次傳遞的全是5個字節的,這和計算的結果幾乎完全相同。
同理“ls | xargs rm -rf”也是一樣的,如果參數列表非常大,則每次傳遞128K的參數給rm。
創建文件名包含分行符的文件
創建文件名包含空格的文件是一件很輕松的事情,但是想創建包含制表符、分行符甚至是其他特殊符號的文件呢?
因為xargs允許傳遞參數到命令的任意參數位,並且傳遞的參數還可以變換為包含各種形式的特殊符號,所以使用它可以輕松實現。例如創建包含分行符的文件。
[root@node2 tmp]# ls | xargs -0 -i touch {}.sh [root@node2 tmp]# ls a a?b?c?d?logdir?one?one_space.log?shdir?space.log?test?.sh b c d logdir one one_space.log shdir space.log test
看上去只是有幾個問號,但是使用?是無法定位它的。
[root@xuexi tmp]# find -name "*[\?]*" #搜索沒結果 或者 [root@xuexi tmp]# rm -rf a #按兩次tab鍵
a/
a^Jb^Jc^Jd^Jlogdir^Jone space.log^Jshdir^Jsh.txt^Jtest^Jvmware-root^J.sh
現在使用xargs就可以輕松顯示它的文件名。
[root@node2 tmp]# ls | xargs -0 a a b c d logdir one one_space.log shdir space.log test .sh b c d logdir one one_space.log shdir space.log test
不能直接使用xargs顯示,因為它會壓縮空白符號成空格。
[root@xuexi tmp]# ls | xargs
a a b c d logdir one space.log shdir sh.txt test vmware-root .sh b c d logdir one space.log shdir sh.txt test vmware-root
刪除它。
[root@xuexi tmp]# rm -f a*.sh
如果想創建文件名只包含下面結果的abcd前四行的.sh文件呢?
[root@xuexi tmp]# ls | xargs -0
a
b
c
d
logdir
one space.log
shdir
sh.txt
test
vmware-root
參考下面的。
[root@xuexi tmp]# ls | xargs -n 1 -e"logdir" | xargs -0 -i touch {}.sh
這就需要理解前面介紹的xargs的分割和傳遞方法了。
也可以使用下面更簡單容易理解的:
[root@xuexi tmp]# ls | head -n 4 | xargs -0 -i touch {}.sh [root@xuexi tmp]# echo -e "a\nb\nc\nd" | xargs -0 -i touch {}.log
那么以相同的方法創建文件名中包含制表符的文件就easy了。
[root@xuexi tmp]# echo -e "a\tb\tc\td" | xargs -0 -i touch {}.log
1.13 高速並發處理之:xargs -P
使用xargs的分批行為,除了可以解決一些問題,還可以一次性將多個分批交給不同進程去處理,這些進程可以使用多個cpu執行,效率可謂大幅提高。
"-P N"選項可以指定並行處理的進程數量為N。不指定"-P"時,默認為1個處理進程,也就是串行執行。指定為0時,將盡可能多地開啟進程數量。當xargs正在運行時(也就是還有分批正在處理),可以發送SIGUSR1信號給xargs進程,表示增加一個處理進程,同樣,可以向xargs進程發送SIGUSR2進程,表示減少一個處理進程。但需要注意,即使發送SIGUSR2信號,xargs也不會中斷正在執行任務的進程,也就是說,在某個進程處理當前分批任務結束之前,不會被中斷,只有當前分批任務執行完畢,准備接下一個分批任務時,才會被xargs給殺掉。
例如,一個簡單的sleep命令,在不使用"-P"的時候,默認是一個進程按批的先后進行處理:
[root@xuexi ~]# time echo {1..4} | xargs -n 1 sleep real 0m10.011s user 0m0.000s sys 0m0.011s
總共用了10秒,因為每批傳一個參數,第一批睡眠1秒,然后第二批睡眠2秒,依次類推,還有3秒、4秒,共1+2+3+4=10秒。
如果使用-P指定4個處理進程:
[root@xuexi ~]# time echo {1..4} | xargs -n 1 -P 4 sleep real 0m4.005s user 0m0.000s sys 0m0.007s
結果總共才用了4秒,因為這4個分批同時交給了4個進程同時處理,所以取最長睡眠時間。
以下是一次並行執行過程中,CPU的使用情況:
[root@xuexi ~]# ps -eo pid,args,psr,pcpu | grep slee[p] 25566 xargs -n 1 -P 5 sleep 3 0.0 25567 sleep 20 1 0.0 25568 sleep 21 2 0.0 25569 sleep 22 0 0.0 25570 sleep 23 2 0.0 25571 sleep 24 3 0.0
在上面的結果中,啟動了5個sleep進程,這5個進程分別用了cpu0、cpu1、cpu2和cpu3共4個cpu,因為我的虛擬機只給分配了4核心cpu。
那么,xargs的哪些選項可以通過"-P"的並行能力來提升效率?其實,只要能分批的選項,都可以使用"-P",包括"-n"、"-L"和"-i"。在man xagrs中,只提到了"-n"和"-L"可以結合"-P",並沒有提到"-i",但我前面已經驗證過了,"-i"其實也是分批行為,也能結合"-P"的並行功能使用。
下面的示例,可以驗證"-i -P"結合:
[root@xuexi ~]# time echo -n {1..4} | xargs -d" " -i -P 4 sleep {} real 0m4.003s user 0m0.000s sys 0m0.005s
如果需要發送信號,來增、減並行進程數量,可以向xargs進程發送SIGUSR1和SIGUSR2信號,例如:
kill -USR1 `pgrep -f "xargs"`
雖然xargs提供了這樣的並行處理能力,但說實話,用到的機會並不多,它畢竟只是一個參數傳遞工具。我也專門寫了一篇關於xargs高效並行的文章,見shell高效處理文本:xargs並行處理。另一個gnu工具parallel(裝上epel源,yum -y install parallel即可)在並行處理上用的比較多,比如對一個巨大的文件利用sort進行排序時,使用parallel並行排序,可以大幅提升效率,即使sort自身已經足夠完美。非常幸運的是,在你把xargs學到了這里,使用parallel將非常簡單,因為它的選項和行為模式和xargs是基本一致的。
1.14 xargs的不足之處(此處必看)
其實是xargs的限制和缺點,但因為通過"-i"選項方便演示,所以此處使用"-i"選項。注意,不是"-i"選項的缺陷。
由於xargs -i傳遞數據時是在shell執行xargs命令的時候,根據shell解析命令行的流程 ,xargs后的命令如果有依賴於待傳遞數據的表達式,則無法正確執行。
例如,無法通過xargs傳遞數值做正確的算術擴展:
[root@xuexi logdir]# echo 1 | xargs -I "x" echo $((2*x))
0
無法將數據傳遞到命令替換中。
[root@xuexi ~]# echo /etc/fstab | xargs -i `cat {}` cat: {}: No such file or directory
參考下圖的shell命令行解析過程。
這時要通過xargs正確實現目標,只能改變方法或尋找一些小技巧,例如:
[root@node2 tmp]# echo 1 | xargs -i expr 2 \* {} 2 [root@node2 tmp]# echo /etc/fstab | xargs -i cat $(echo {}) # # /etc/fstab # Created by anaconda on Thu Aug 15 22:32:28 2019 # # Accessible filesystems, by reference, are maintained under '/dev/disk' # See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info # /dev/mapper/centos-root / xfs defaults 0 0 UUID=8fac2641-fdea-4838-bd47-fd7b307bfb0b /boot xfs defaults 0 0 /dev/mapper/centos-swap swap swap defaults 0 0
另外,xargs無法處理bash內置命令。例如:
[root@node2 tmp]# echo /etc | xargs -i cd {}