首頁 > 軟體

xargs的原理剖析及用法詳解

2020-06-16 17:04:21

學習這個xargs花了很長時間,在網上翻了很久也查了很多書關於xargs的介紹,都只是簡單的介紹了它的幾個用法,卻沒有介紹它工作的原理,man也只有簡單的介紹,並沒有說各個選項之間配合時的情況。所以我只能自己探索了,探索的路上確實充滿了荊棘,不斷的總結卻不斷的被實驗推翻,每當以為自己得出了結論,卻往往發現不夠完善,所以我自己也是邊測試邊刪改完成這篇學習記錄,但是不得不說這過程充滿了樂趣。

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

這一整篇是我個人的總結和記錄,由於完全是自己一個人在短短幾天內探索出來的,所以難免會有所遺漏、錯誤,甚至可能是誤人的結論,萬望見諒。如果有朋友發現了其中的問題盼請不吝指出,本人將感激不盡。


本文目錄:

1.1 為什麼需要xargs
1.2 文字意義上的符號和標記意義上的符號
1.3 分割行為之:xargs
1.4 使用xargs -p或xargs -t觀察命令執行過程
1.5 分割行為之:xargs -d
1.6 分割行為之:xargs -0
1.7 分批行為
  1.7.1 xargs -n
  1.7.2 xargs -L
  1.7.3 xargs -i
  1.7.4 分批選項的優先順序
  1.7.5 分批選項的一個典型應用
1.8 終止行為之:xargs -E
1.9 xargs的處理總結
1.10 xargs與find的結合
1.11 xargs -s之為什麼ls | xargs rm -rf能執行成功?
1.12 建立檔名包含分行符的檔案


 

1.1 為什麼需要xargs

管道實現的是將前面的stdout作為後面的stdin,但是有些命令不接受管道的傳遞方式,最常見的就是ls命令。有些時候命令希望管道傳遞的是引數,但是直接用管道有時無法傳遞到命令的引數位,這時候需要xargs,xargs實現的是將管道傳輸過來的stdin進行處理然後傳遞到命令的引數位上。也就是說xargs完成了兩個行為:處理管道傳輸過來的stdin;將處理後的傳遞到正確的位置上。

可以試試執行下面的幾條命令,應該能很好理解xargs的作用了:

[root@linuxidc tmp]# echo "/etc/inittab" | cat   # 直接將標準輸入的內容傳遞給cat
[root@linuxidc tmp]# echo "/etc/inittab" | xargs cat   # 將標準輸入的內容經過xargs處理後傳遞給cat
[root@linuxidc 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個中的每一個,處理完了處理下一個傳遞過來的批,如下圖。

 

但是應該注意的是,儘管實現了分批次處理,但是並沒有提高任何效率,因為分批傳遞之後還是一次執行一個。而且有時候分批傳遞後是作為一個引數的整體,並不會將分批中的資訊分段執行。實現分批傳遞的目的僅僅是為了解決一些問題。

剩下的就是處理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 --> -i逐漸變高,當指定高優先順序的分批選項會覆蓋低優先順序的分批選項,所以我覺得-i也是分批選項。並且後文中我也將其當成分批選項來介紹和說明。

當然上述只是一個概括,更具體的還要看具體的選項介紹,而且很可能一個xargs中用不到這麼多選項,但是理解這個很重要,否則在分割分批和傳遞上很容易出現疑惑。

 

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

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

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

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

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

因為翻了百度、谷歌和一些書都沒說這些方面的分類。但文字和非文字的符號在xargs分割的時候確實是區別對待的,所以我覺得有必要給個稱呼好參照並說明它們,也就是說以上稱呼完全是我個人的稱呼。

 

1.3 分割行為之:xargs

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

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

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

the first sh the second line

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

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

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

如果不指定分批選項,xargs的一整行結果將作為一個整體輸出,而不是分隔開的。也許看處理的結果感覺是分開處理的,例如下面的第一個命令,但是這是因為ls允許接受多個空格分開的引數,執行第二個命令,可以證明它確實是將整行作為整體傳輸給命令的。

[root@linuxidc 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@linuxidc 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@linuxidc tmp]# ls   #one space.log是一個檔案的檔名,只是包含了空格

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

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

a b

c d

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

space.log shdir

sh.txt test

vmware-root x.txt

[root@linuxidc 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@linuxidc 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@linuxidc tmp]# ls | xargs -n 2 -t

/bin/echo a b  # 先列印一次命令,表示這一次只echo兩個引數:a和b

a b

/bin/echo c d  # 表示這次只列印c和d

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@linuxidc 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@linuxidc tmp]# ls

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

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

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

如果使用xargs -d時不指定分批選項,則整個結果將作為整體輸出。

[root@linuxidc 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@linuxidc 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@linuxidc 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@linuxidc 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@linuxidc tmp]#

 


IT145.com E-mail:sddin#qq.com