xargs原理剖析及用法詳解

bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html html


學習這個xargs花了很長時間,在網上翻了好久也查了不少書關於xargs的介紹,都只是簡單的介紹了它的幾個用法,卻沒有介紹它工做的原理,man也只有簡單的介紹,並無說各個選項之間配合時的狀況。因此我只能本身探索了,探索的路上確實充滿了荊棘,不斷的總結卻不斷的被實驗推翻,每當覺得本身得出告終論,卻每每發現不夠完善,因此我本身也是邊測試邊刪改完成這篇學習記錄,可是不得不說這過程充滿了樂趣。shell

我的感受xargs的基本用法很簡單,它的選項實現的功能也很簡單,可是多個選項配合時問題就變得很難很難,常常發現結果和預期不一樣。bash

這一整篇是我我的的總結和記錄,因爲徹底是本身一我的在短短几天內探索出來的,因此不免會有所遺漏、錯誤,甚至多是誤人的結論,萬望見諒。若是有朋友發現了其中的問題盼請不吝指出,本人將感激涕零。 併發

1.1 爲何須要xargs

管道實現的是將前面的stdout做爲後面的stdin,可是有些命令不接受管道的傳遞方式,最多見的就是ls命令。有些時候命令但願管道傳遞的是參數,可是直接用管道有時沒法傳遞到命令的參數位,這時候須要xargs,xargs實現的是將管道傳輸過來的stdin進行處理而後傳遞到命令的參數位上。也就是說xargs完成了兩個行爲:處理管道傳輸過來的stdin;將處理後的傳遞到正確的位置上。ide

能夠試試運行下面的幾條命令,應該能很好理解xargs的做用了:工具

[root@xuexi tmp]# echo "/etc/inittab" | cat   # 直接將標準輸入的內容傳遞給cat
[root@xuexi tmp]# echo "/etc/inittab" | xargs cat   # 將標準輸入的內容通過xargs處理後傳遞給cat
[root@xuexi tmp]# find /etc -maxdepth 1 -name "*.conf" -print0 | xargs -0 -i grep "hostname" -l {} # 將搜索的文件傳遞給grep的參數位進行搜索,若不使用xargs,則grep將報錯

xargs的做用不只僅限於簡單的stdin傳遞到命令的參數位,它還能夠將stdin或者文件stdin分割成批,每一個批中有不少分割片斷,而後將這些片斷按批交給xargs後面的命令進行處理。學習

通俗的講就是原來只能一個一個傳遞,分批能夠實現10個10個傳遞,每傳遞一次,xargs後面的命令處理這10箇中的每個,處理完了處理下一個傳遞過來的批,以下圖。測試

1

可是應該注意的是,儘管實現了分批處理,可是默認狀況下並無提升任何效率,由於分批傳遞以後仍是一次執行一個。並且有時候分批傳遞後是做爲一個參數的總體,並不會將分批中的信息分段執行。這樣看來,實現分批傳遞的目的僅僅是爲了解決一些問題。但事實上,xargs提供了"-P"選項,用於指定並行執行的數量(默認只有一個處理進程,不會提高效率,能夠指定爲N個子進程,或者指定爲0表示儘量多地利用CPU),這樣就能讓分批操做更好地利用多核cpu,從而提高效率。例如上面分紅了兩批,指定"-P 2"能夠併發執行這兩個批,而非執行完第一批再執行第二批。關於並行處理的詳細內容,見後文:高速並行處理之:xargs -Pspa

剩下的就是處理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中用不到這麼多選項,可是理解這個很重要,不然在分割分批和傳遞上很容易出現疑惑。

1.2 文本意義上的符號和標記意義上的符號

在解釋xargs和它的各類選項以前,我想先介紹一個貫穿xargs命令的符號分類:文本意義上的空格、製表符、反斜線、引號和非文本意義上的符號。我以爲理解它們是理解xargs分割和分批原理的關鍵。

文本意義上的空格、製表符、反斜線、引號:未經處理就已經存在的符號,例如文本的內容中出現這些符號以及在文件名上出現了這些符號都是文本意義上的。與之相對的是非文本意義的符號,因爲在網上沒找到相似的文章和解釋,因此我我的稱之爲標記意義上的符號:處理後出現的符號,例如ls命令的結果中每一個文件之間的製表符,它本來是不存在的,只是ls命令處理後的顯示方式。還包括每一個命令結果的最後的換行符,文件內容的最後一行結尾的換行符

以下圖,屬於標記意義上的符號都用紅色圓圈標記出來了。

image

其實它們的關係有點相似於字面意義的符號和特殊符號之間的關係,就像有時候特殊符號須要進行轉義才能表示爲普通符號。

由於翻了百度、谷歌和一些書都沒說這些方面的分類。但文本和非文本的符號在xargs分割的時候確實是區別對待的,因此我以爲有必要給個稱呼好引用並說明它們,也就是說以上稱呼徹底是我我的的稱呼。

1.3 分割行爲之:xargs

[root@xuexi tmp]# cd /tmp
[root@xuexi tmp]# rm -fr *
[root@xuexi tmp]# mkdir a b c d test logdir shdir
[root@xuexi tmp]# touch "one space.log"
[root@xuexi tmp]# touch logdir/{1..10}.log
[root@xuexi tmp]# touch shdir/{1..5}.sh
[root@xuexi tmp]# echo "the second sh the second line" > shdir/2.sh 
[root@xuexi tmp]# cat <<eof>shdir/1.sh  
> the first sh
> the second line
> eof

對於xargs,它將接收到的stdout處理後傳遞到xargs後面的命令參數位,不寫命令時默認的命令是echo。

[root@xuexi tmp]# cat shdir/1.sh | xargs
the first sh the second line
[root@xuexi tmp]#
cat shdir/1.sh | xargs echo
the first sh the second line

將分行處理掉不是echo實現的,而是管道傳遞過來的stdin通過xargs處理後的:將全部空格、製表符和分行符都替換爲空格並壓縮到一行上顯示,這一整行將做爲一個總體,這個總體的全部空格屬性繼承xargs處理前的符號屬性,即原來是文本意義的或標記意義的在替換爲空格後符號屬性不變。這個總體可能直接交給命令或者做爲stdout經過管道傳遞給管道右邊的命令,這時結果將做爲總體傳遞,也可能被xargs同時指定的分批選項分批處理。

若是想要保存製表符、空格等特殊符號,須要將它們用單引號或雙引號包圍起來,可是單雙引號(和反斜線)都會被xargs去掉。

另外通過個人測試,單引號和雙引號的存在讓處理變的很不受控制,常常會影響正常的分割和處理。

若是不指定分批選項,xargs的一整行結果將做爲一個總體輸出,而不是分隔開的。也許看處理的結果感受是分開處理的,例以下面的第一個命令,可是這是由於ls容許接受多個空格分開的參數,執行第二個命令,能夠證實它確實是將整行做爲總體傳輸給命令的。

[root@xuexi tmp]# find /tmp -maxdepth 1 | xargs ls

/tmp/sh.txt

 

/tmp:

a  b  c  d  logdir   shdir  sh.txt  test

 

/tmp/a:

 

/tmp/b:

 

/tmp/c:

 

/tmp/d:

 

/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  hell sh.txt

 

/tmp/test:

[root@xuexi tmp]# find /tmp -maxdepth 1 | xargs -p ls   # -p選項後面有解釋

ls /tmp /tmp/x.txt /tmp/logdir /tmp/b /tmp/test /tmp/d /tmp/vmware-root /tmp/sh.txt /tmp/c /tmp/shdir /tmp/a /tmp/one space.log /tmp/.ICE-unix ?...

若是對獨立的xargs指定分批選項,則有兩種分批可能:指定-n時按空格分段,而後劃批,無論是文本意義的空格仍是標記意義的空格,只要是空格都是-n的操做對象;指定-L或者-i時按段劃批,文本意義的符號不被處理。

[root@xuexi tmp]# ls   #one space.log是一個文件的文件名,只是包含了空格

a  b  c  d  logdir  one space.log  shdir  sh.txt  test  vmware-root   x.txt

[root@xuexi tmp]# ls | xargs -n 2

a b

c d

logdir one    #  onespace.log分割開了,說明-n是按空格分割的

space.log shdir

sh.txt test

vmware-root x.txt

[root@xuexi tmp]# ls | xargs -L 2

a b

c d

logdir one space.log  #  one space.log做爲一個分段,文件名中的空格沒有分割這個段

shdir sh.txt

test vmware-root

x.txt

[root@xuexi tmp]# ls | xargs -i -p echo {}

echo a ?...

echo b ?...

echo c ?...

echo d ?...

echo logdir ?...

echo one space.log ?...  #  one space.log也沒有被文件名中的空格分割

echo shdir ?...

echo sh.txt ?...

echo test ?...

echo vmware-root ?...

echo x.txt ?...

1.4 使用xargs -p或xargs -t觀察命令的執行過程

使用-p選項是交互詢問式的,只有每次詢問的時候輸入y(或yes)纔會執行,直接按enter鍵是不會執行的。

使用-t選項是在每次執行xargs後面的命令都會先在stderr上打印一遍命令的執行過程而後才正式執行。

使用-p或-t選項就能夠根據xargs後命令的執行順序進行推測,xargs是如何分段、分批以及如何傳遞的,這經過它們有助於理解xargs的各類選項。

[root@xuexi tmp]# ls | xargs -n 2 -t

/bin/echo a b  #  先打印一次命令,表示這一次只echo兩個參數:ab

a b

/bin/echo c d  #  表示此次只打印cd

c d

/bin/echo logdir one

logdir one

/bin/echo space.log shdir

space.log shdir

/bin/echo sh.txt test

sh.txt test

/bin/echo vmware-root

vmware-root

[root@xuexi tmp]# ls | xargs -n 2 -p

/bin/echo a b ?...y   #  詢問是否echo a b

/bin/echo c d ?...a b

y       #  詢問是否echo c d,後面的...a b指示了echo c d是在前一個結果的基礎上接着執行的

/bin/echo logdir one ?...c d

y

/bin/echo space.log shdir ?...logdir one

y

/bin/echo sh.txt test ?...space.log shdir

y

/bin/echo vmware-root ?...sh.txt test

y

vmware-root

從上面的-t和-p的結果上均可以知道每次傳遞兩個參數。

1.5 分割行爲之: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@xuexi tmp]# ls

a  b  c  d  logdir   one space.log  shdir  sh.txt  test  vmware-root

[root@xuexi tmp]# ls | xargs -d"o"  #指定字母"o"爲分隔符

分段結果如圖所示,圖中每一個封閉體都是一個分段,這些分段裏可能包含了分行,可能包含了空格。

3

若是使用xargs -d時不指定分批選項,則整個結果將做爲總體輸出。

[root@xuexi tmp]# ls | xargs -d"o" -p

/bin/echo a

b

c

d

l gdir

 ne space.l g

shdir

sh.txt

test

vmware-r  t

x.txt

 ?...

若是指定了分批選項,則按照-d指定的分隔符分段後的段分批,這時使用-n、-L或-i的結果是同樣的。例如使用-n選項來觀察是如何分批的。

[root@xuexi tmp]# ls | xargs -d"o" -n 2 -t

/bin/echo a

b

c

d

l gdir   #  每兩段是一個批。

  

a

b

c

d

l gdir

  # 注意這裏有個空行。是由於段的分隔符處於下一段的行開頭,它的前面有個\n符號會按符號輸出。

/bin/echo ne space.l g

shdir

sh.txt

test

vmware-r  #  打印中間兩段

ne space.l g

shdir

sh.txt

test

vmware-r

/bin/echo  t  #  打印最後一段,

 

 t   #  注意t前面有空格,由於是兩個分隔符o連在一塊兒分割的,因此前面有個空格須要輸出。

 

下面是最終顯示結果。

[root@xuexi tmp]# ls | xargs -d"o" -n 2

a

b

c

d

l gdir

 

ne space.l g

shdir

sh.txt

test

vmware-r

 t

 

再看看-n 1的輸出。

[root@xuexi tmp]# ls | xargs -d"o" -n 1

a

b

c

d

l

gdir

 

ne space.l

g

shdir

sh.txt

test

vmware-r

 

t

 

[root@xuexi tmp]#

1.6 分割行爲之: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@xuexi tmp]# touch "one space.log"
[root@xuexi tmp]# ls | tr " " "\t" | xargs -0  #忽略文本意義上的製表符

a

b

c

d

logdir

one     space.log

shdir

sh.txt

test

vmware-root

              #  注意有空行,由於命令結尾是一個標記意義上換行符號

[root@xuexi tmp]# ls | tr " " " " | xargs -0   #忽略文本意義上的空格

a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

           #  注意有空行

若是檢測到\0而沒有使用-0或--null處理則給出警告。注意警告後執行哪些文件。

[root@xuexi tmp]# ls | tr " " "\0"

a

b

c

d

logdir

onespace.log   #  這裏其實是one\0space.log

shdir

sh.txt

test

vmware-root

[root@xuexi tmp]# ls | tr " " "\0" | xargs

xargs: Warning: a NUL character occurred in the input.  It cannot be passed through in the argument list.  Did you mean to use the --null option?

a b c d logdir one shdir sh.txt test vmware-root   #  執行時將space.log忽略了,其他都執行

再例如,將全部的換行符換成null字符,結果中除了最前面的字母a和因爲空格而不被\0影響的space.log,其他的因爲所有有\0所有被忽略。

[root@xuexi tmp]#  ls | tr "\n" "\0"

a bcdlogdirone space.logshdirsh.txttestvmware-root   #  只有a的前面和space.log的前面是沒有\0

[root@xuexi tmp]#  ls | tr "\n" "\0" | xargs

xargs: Warning: a NUL character occurred in the input.  It cannot be passed through in the argument list.   Did you mean to use the --null option?

a space.log   #  因此只執行這兩個

使用-0或--null來解決問題,也可使用等價的xargs -d"\0"來解決。

[root@xuexi tmp]# ls | tr "\n" "\0" | xargs -0
或者
[root@xuexi tmp]# ls | tr "\n" "\0" | xargs -d"\0"

a b c d logdir one space.log shdir sh.txt test vmware-root

若是使用xargs -0時不指定分批選項(-n、-L、-i),則處理後的結果將做爲一個總體輸出。

若是指定了分批選項,而且檢測到了null字符,則以\0位的空格分段劃批,這時使用-n、-L或-i的結果是同樣的。例如使用-n選項來觀察是如何分批的。

[root@xuexi tmp]# ls | tr "\n" "\0" | xargs -0 -n 3

a b c

d logdir one space.log

shdir sh.txt test

vmware-root x.txt

若是指定了分批選項,但沒有檢測到null字符,則整個結果將稱爲一個不可分割總體,這時使用分批選項是徹底無心義的。

[root@xuexi tmp]# ls | xargs -0 -n 3 -p

/bin/echo a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

x.txt

 ?...

1.7 分批行爲

分批用於指定每次傳遞多少個分段。有三種分批選項:-n,-L和-i。在本文的開頭已經指明瞭爲何-i是分批選項,可是這裏仍是要介紹它邏輯上定義的功能:參數替換。

既然三種選項都是分批選項,若是在一個xargs中使用了多個分批選項,則它們之間必然會衝突,它們的規則是寫在後面的生效,前面的分批選項被忽略

1.7.1 xargs -n

xargs -n分兩種狀況:和獨立的xargs一塊兒使用,這時按照每一個空格分段劃批;和xargs -d或xargs -0一塊兒使用,這時按段分批,即不以空格、製表符和分行符分段劃批。

[root@xuexi tmp]# ls | xargs -n 3 -p   #和獨立的xargs一塊兒使用,以空格分段劃批

/bin/echo a b c ?...

/bin/echo d logdir one ?...   #  onespace.log被割開了

/bin/echo space.log shdir sh.txt ?...

/bin/echo test vmware-root x.txt ?...

/bin/echo ?...

[root@xuexi tmp]# ls | xargs -d"o" -n 3 -p  # 和xargs -d一塊兒使用,按段分批

/bin/echo a

b

c

d

l gdir

 ne space.l ?...

/bin/echo g

shdir

sh.txt

test

vmware-r  t

x.txt

 ?...

/bin/echo ?...

1.7.2 xargs -L

和-n選項相似,惟一的區別是-L永遠是按段劃批,而-n在和獨立的xargs一塊兒使用時是按空格分段劃批的。

該選項的一個同義詞是-l,可是man推薦使用-L替代-l,由於-L符合POSIX標準,而-l不符合。使用--max-lines也能夠。

也許你man xargs時發現-L選項是指定傳遞時最大傳遞行數量的,man的結果以下圖。可是經過下面的實驗能夠驗證其實-L是指定傳遞的最大段數,也就是分批。

4

[root@xuexi tmp]# ls | xargs -L 3 -p   #若是是指定傳遞的最大行數量,則一行就輸出完了,這裏卻分了多行輸出

/bin/echo a b c ?... 

/bin/echo d logdir one space.log ?...  #  這裏能夠證實-L-n的區別

/bin/echo shdir sh.txt test ?...

/bin/echo vmware-root x.txt ?...

[root@xuexi tmp]# ls | xargs -d"o" -L 3 -p  # 這就更能證實是指定最大傳遞的段數量了

/bin/echo a

b

c

d

l gdir

 ne space.l ?...

/bin/echo g

shdir

sh.txt

test

vmware-r  t

x.txt

 ?...

1.7.3 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@xuexi tmp]# ls logdir/

10.log  1.log  2.log  3.log   4.log  5.log  6.log  7.log  8.log  9.log

[root@xuexi tmp]# ls logdir/ | xargs -i mv ./logdir/{} ./logdir/{}.bak   # 將分段傳遞到多個參數位
[root@xuexi 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@xuexi tmp]# ls | xargs echo

a b c d logdir one space.log shdir sh.txt test vmware-root

[root@xuexi tmp]# ls | xargs -i echo {}

a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

既然使用-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@xuexi tmp]# cat <<eof>logdir/1.log
> 1 2 3
> 4 5 6
> 7 8 9
> 10
> eof

再使用xargs -i分批傳遞。

[root@xuexi 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@xuexi tmp]# echo {1..10} | xargs -n 3 | xargs -i echo "start {} end"

1.7.4 分批選項的生效規則

-i、-L、-n選項都是分批選項。它們的生效規則是,誰指定在後面,誰就生效。

下面給出證實。

下面-i放在-n、-L以後,結果是-n、-L被忽略。

[root@xuexi tmp]# ls | xargs -d"o" -n 2 -p -i echo {}

echo a

b

c

d

l ?...  #  說明是一段一段輸出,而不是兩段一批輸出,即-n選項被忽略

echo gdir

 ?...

echo ne space.l ?...

echo g

shdir

sh.txt

test

vmware-r ?...

echo  ?...

echo t

x.txt

 ?...

[root@xuexi tmp]# ls | xargs -d"o" -L 3 -i -p echo {}   # 和上面的結果是如出一轍的,說明-L選項被忽略

echo a

b

c

d

l ?...

echo gdir

 ?...

echo ne space.l ?...

echo g

shdir

sh.txt

test

vmware-r ?...

echo  ?...

echo t

x.txt

 ?...

下面是-L放在-n後,結果是-n被忽略。

[root@xuexi tmp]# ls | xargs -d"o" -n 2 -p -L 1 echo  # 結果也是一段一段輸出的,說明-n選項被忽略

echo a

b

c

d

l ?...

echo gdir

 ?...

echo ne space.l ?...

echo g

shdir

sh.txt

test

vmware-r ?...

echo  ?...

echo t

x.txt

 ?...

根據上面的證實,其實也就給出了我認爲-i選項是分批選項的理由,由於它覆蓋了-n和-L,實際上在新的man xargs中已經給出瞭解釋,它隱含"-L 1"。其實若是說-i包含分批並傳遞這兩個做用更嚴格一點

1.7.5 分批選項的一個典型應用

分批選項有時特別有用,例如腳本規定每次只能傳輸三個參數。有時候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

1.8 終止行爲之: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 ?...

1.9 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時,整個結果做爲不可分割總體,使用分批選項是無心義的

不指定

結果做爲總體輸出

1.10 xargs與find的結合

xargs和find同屬於一個rpm包findutils,xargs本來就是爲find而開發的,它們之間的配合應當是完美無缺的。

通常狀況下它們隨意結合都無所謂,按正常方式進行便可。可是當刪除文件時,特別須要將文件名含有空白字符的文件歸入考慮。

[root@xuexi tmp]# touch one;touch space.log
[root@xuexi tmp]# ls

a  b  c  d  logdir  one  one space.log  shdir  sh.txt   space.log  test  vmware-root

如今假設經過find搜索到了one space.log。

[root@xuexi 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@xuexi tmp]# find -name "* *.log" -print0 | xargs -0 rm -rf

固然,能使用-0確定也能使用-d了。

[root@xuexi tmp]# find -name "* *.log" -print0 | xargs -d "x" rm -rf     #隨意指定非文件名中的字符都行,不必定非要\0

方法二:不在find上處理,在xargs上處理,只要經過配合-i選項,就能宣告它的總體性。

[root@xuexi tmp]# find -name "* *.log" | xargs -i rm -rf "{}"

相較而言,方法一使用的更普遍更爲人所知,可是方法二更具備通用性,對於非find如ls命令也能夠進行處理。

還可使用tr將find的換行符換成其餘符號再xargs分割配對也行。

除了find -print0能夠輸出\0字符,Linux中還有其餘幾個命令配合參數也能夠實現:locate -0,grep -zgrep -Z,sort -z等

1.11 xargs -s之爲何ls | xargs rm -rf能執行成功?

使用下面的示例配合圖來解釋。

[root@xuexi tmp]# cd logdir
[root@xuexi logdir]# touch {1..1000000}

-bash: /bin/touch: Argument list too long

[root@xuexi 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個字節的,這和計算的結果幾乎徹底相同。

6

7

同理「ls | xargs rm -rf」也是同樣的,若是參數列表很是大,則每次傳遞128K的參數給rm。

1.12 建立文件名包含分行符的文件

建立文件名包含空格的文件是一件很輕鬆的事情,可是想建立包含製表符、分行符甚至是其餘特殊符號的文件呢?

由於xargs容許傳遞參數到命令的任意參數位,而且傳遞的參數還能夠變換爲包含各類形式的特殊符號,因此使用它能夠輕鬆實現。例如建立包含分行符的文件。

[root@xuexi tmp]# ls

a  b  c  d  logdir   one space.log  shdir  sh.txt  test  vmware-root

[root@xuexi tmp]# ls | xargs -0

a

b

c

d

logdir

one space.log

shdir

sh.txt

test

vmware-root

 

在此基礎上建立一個.sh文件,這個文件將奇形怪狀,由於文件名居然包含了分行符(Linux中文件名除了"/"和"\0"外全部字符都容許包含在內)

[root@xuexi tmp]# ls | xargs -0 -i touch {}.sh
[root@xuexi tmp]# ls

a                                                               b   d       one space.log  sh.txt  vmware-root

a?b?c?d?logdir?one space.log?shdir?sh.txt?test?vmware-root?.sh   c  logdir  shdir          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@xuexi tmp]# ls | xargs -0

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

不能直接使用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、cpu一、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@xuexi ~]# echo 1  | xargs -i expr 2 \* {}    # 感謝樓下評論者提供的expr思路
2
[root@xuexi ~]# echo /etc/fstab | xargs -i cat $(echo {}) 

另外,xargs沒法處理bash內置命令。例如:

[root@xuexi ~]# echo /etc  | xargs -i cd {}
xargs: cd: No such file or directory
相關文章
相關標籤/搜索