你真的瞭解xargs命令嗎

shell裏的xargs命令大家都很熟悉,最常見的使用場景就是文件內查找文本

find . -type f -name "*.c" | xargs grep abc
這行命令首先通過find程序在當前目錄(含子目錄)下查找所有.c文件,然後運行grep程序在所有文件內容裏搜索文本"abc"。

A:就是這麼簡單嗎?
B:是啊,很簡單,要不然無名師怎麼會對來訪的程序員說“Unix傳統上認爲一行shell腳本勝過萬行c程序"
A:。。。。。。

可是這總讓人覺得有點不對勁,命令本身沒錯,到底該怎麼去理解這行命令的工作原理呢?a|b 這很好理解,是把前一個命令a的標準輸出與後一個命令b的標準輸入連接起來,那現在find的標準輸出到底是給了xargs還是給了grep,如果是給了xargs,那xargs又是如何把這串文件名傳遞給grep的?每一個文件都調用了一次grep的話那有幾萬個文件,grep程序啓動了幾萬次嗎?

逐步分析,首先一個單獨find命令輸出是換行符分割的文件名,像下面這樣


如果find後面加一個管道 | 則find的所有輸出就作爲後面命令的標準輸入了,這麼說來,xargs命令收到的是一堆的文件名,注意是作爲標準輸入而不是啓動參數,那xargs後續又是怎麼處理的呢?先看下文檔
man xargs
在DESCRIPTION節裏有如下一段說明
xargs reads items from the standard input, delimited by blanks (which can be protected with double or single quotes or a backslash) or newlines, and executes the command (default is /bin/echo)one or more times with any initial-arguments followed by items read from standard input.  Blank lines on the standard input are ignored.
文檔說xargs就是設計用來讀取標準輸入的,並且把讀取的標準輸入的內容作爲參數,啓動後面的命令一次或多次。一次或多次?幾萬次算不算多次?文檔沒有給出明確的解釋,看來只有我們自己試驗了。

現在要構造一個簡單的命令show替換掉grep,這個命令每次被調用都會輸出信息,輸出內容是傳遞給這個命令的參數的個數,這樣我們就可以通過運行下面的程序來觀察show被調用了多少次以及每次傳遞了多少參數給它。注意要把show文件放到環境變量的PATH裏面,保證任何地方都能使用這個命令

運行命令:
find . -type f -name "*.c" | xargs show

show:

#!/bin/bash
echo $#

結果揭曉,每次調用的參數個數都不同,從2000多到4000多看似無規律,show沒有被調用幾萬次,但也不止調用了一次。

根據試驗結果,一個合理的猜測是xargs並不對參數個數進行限制,而是對參數的總長度進行了限制,當參數長度超過某個固定大小時,xargs會第二次啓動後續命令(這裏是show)如此反覆直到所有的輸入都被處理完,這也可以解釋最後剩下的503個參數明顯少於之前的個數,因爲就剩下這麼多了。
通過修改show命令來驗證我們的猜測,show修改爲下面的樣子,這個命令可以統計出每次傳入參數的個數和字符總長度

#!/bin/bash
echo $@ | wc
結果在下面這裏,猜測被證明是正確的



每次傳遞過來的參數的總長度都在131012~131067之間,浮動很小,這是因爲在截斷文件名時,必須保證最後一個文件名的完整性,也就是說不能把一個文件名從中間截成兩半,所以長度有細微差別。

所以結論已經出來了:xargs有一個固定大小的參數緩衝區。當參數長度小於緩衝區時,只調用一次後面的命令,將標準輸入讀取的內容作爲參數傳遞給後續命令來運行它,當參數大於緩衝區長度時,就把滿足一個緩衝區長的參數送給後面的命令啓動(結尾會保證不截斷),反覆直到所有的輸入都被讀取完畢。

最後注意一點,如果已經爲xargs調用的命令提供了一個參數(就像之前的abc),那xargs會將自己讀取的內容放到書寫的參數之後,作爲第2-n個參數出現,也就是說按照之前的調用方式使用find/grep

find . -type f -name "*.c" | xargs grep abc
後半段實際是
grep abc file1.c file2.c file3.c .....

如果我想把xargs的參數放到abc的前面,讓abc作爲最後一個參數,該怎麼做呢?man xargs裏有解釋,可以這麼做

find ... | xargs -i grep {} abc

-i參數後使用符號{}來表示輸出的參數,在命令後的合適位置寫上這個{}即可。

花費時間探究xargs的工作原理,除了滿足好奇心,對我們的工作有實際意義嗎?

答案當然是肯定的,我們如果要提供程序給別人使用,要儘量讓自己的程序更符合Unix風格,具備讀取多個啓動參數的能力,同時也具備從管道讀取參數的能力,這種符合Unix風格的命令別人使用起來會非常方便,甚至組合出你在設計時都沒想到的功能。就像最普通的grep程序那樣

find . -type f -name "*.c" | xargs grep abc //從啓動參數讀取,過濾文件內容
find . -type f -name "*.c" | grep abc       //從管道讀取,過濾文件名

發佈了28 篇原創文章 · 獲贊 46 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章