[英]Pipe, standard input and command line arguments in Bash
考慮:
command1 | command2
command1 的輸出是用作 command2 的標准輸入還是用作 command2 的命令行參數?
例如,
cat test.sh | grep "hehe"
不使用管道的等效形式是什么?
我試過
grep "hehe" $(cat test.sh)
它似乎不正確。
grep "hehe" < test.sh
輸入重定向 - 當然,僅適用於單個文件,而cat
適用於任意數量的輸入文件。
考慮符號:
grep "hehe" $(cat test.sh)
grep "hehe" `cat test.sh`
這些在這種情況下是等效的; 在嵌套使用中使用“ $(cmd)
”符號要容易得多,例如:
x=$(dirname $(dirname $(which gcc)))
x=`dirname \`dirname \\\`which gcc\\\`\``
(這為您提供了安裝 GCC 的基本目錄,以防您想知道。)
在grep
示例中,所發生的情況是讀取test.sh
的內容並將其拆分為以空格分隔的單詞,並且每個這樣的單詞都作為參數提供給grep
。 由於grep
處理"hehe"
之后的單詞(當然,其中grep
沒有看到雙引號 - 在這種情況下不需要它們;作為一般規則,使用單引號而不是雙引號,尤其是在復雜的字符串中,例如經常使用 shell 元字符的正則表達式)...正如我所說, grep
將"hehe"
之后的單詞視為文件名,並嘗試打開每個文件,通常會因為文件不存在而慘淡失敗。 這就是該符號在這種情況下不合適的原因。
在重新審視這個問題之后,還有更多可以說的——還沒有說。
首先,許多 Unix 命令被設計為用作“過濾器”; 他們從一些文件中讀取輸入,以某種方式對其進行轉換,然后將結果寫入標准輸出。 此類命令專為在命令管道中使用而設計。 例子包括:
所有這些過濾器都具有相同的一般行為:它們采用命令行選項來控制它們的行為,然后它們要么讀取指定為命令行參數的文件,要么讀取它們的標准輸入,如果沒有這樣的參數。 有些(如sort
)可以有選項來控制它們的輸出而不是標准輸出,但這種情況相對不常見。
有一些純過濾器 - tr
就是其中之一 - 嚴格讀取標准輸入並寫入標准輸出。
其他命令有不同的行為。 Eric Raymond 在“ The Art of UNIX Programming ”中提供了命令類型的分類法。
一些命令在標准輸出上生成文件名列表 - 兩個經典命令是ls
和find
。
有時,您希望將文件名生成器的輸出應用為過濾器的命令行參數。 有一個程序可以自動執行此操作 - 它是xargs
。
通常,您將使用:
find . -name '*.[chyl]' | xargs grep -n magic_name /dev/null
這將生成擴展名為“ .c
”、“. .h
”、“. .y
”和“ .l
”(C 源、頭文件、Yacc 和 Lex 文件)的文件的完整列表。 當列表由xargs
讀取時,它將創建命令行,以grep -n magic_name /dev/null
開頭,並將每個單詞(由空格分隔)作為參數。
在過去,Unix 文件名不包含空格。 在 Mac 和 Windows 的影響下,這樣的空間現在已經司空見慣。 find
和xargs
的 GNU 版本具有處理此問題的補充選項:
find . -name '*.[chyl]' -print0 | xargs -0 grep -n magic_name /dev/null
' -print0
' 選項的意思是“打印以 NUL '\\0' 結尾的文件名”(因為不能出現在(簡單)文件名中的唯一字符是 '/' 和 NUL,顯然,'/' 可以出現在路徑名中)。 相應的“ -0
”告訴xargs
查找以 NUL 結尾的名稱,而不是空格分隔的名稱。
另一種重定向形式是進程替換。
grep "hehe" <(cat test.sh)
相當於:
grep "hehe" test.sh
兩者都查看test.sh
本身的內容。
雖然,正如已經指出的,這個命令:
grep "hehe" $(cat test.sh)
在test.sh
查找文件名並將它們用作grep
參數。 所以如果test.sh
包含:
scriptone
scripttwo
然后grep
將在每個文件的內容中查找“hehe”。
管道和命令行參數是不同形式的輸入,不可互換。 如果一個程序允許您擁有兩者的等效形式,則只能選擇該程序。 (源代碼,命令行參數顯示為一個可變文本,而管顯示為打開的文件,包括標准輸入和標准輸出。Bash的I / O重定向的語法,這里lateron使用,在技術上不屬於命令行參數,即使在命令行上寫在它們旁邊……)
但讓我們學究,也回答這個:
答案: cat test.sh | grep "hehe"
cat test.sh | grep "hehe"
相當於
grep "hehe" < <(cat test.sh)
說明:
管道將一個命令的標准輸出重定向到另一個命令的標准輸入。 要設置 stdin 的來源,我們可以使用輸入重定向 ( < …
) 而不是使用管道字符。
但是,僅使用輸入重定向( grep "hehe" < test.sh
)並不等同於管道,因為它使用文件作為 stdin 的源,而管道使用輸出命令( cat test.sh
)。 因此,此外,我們添加了進程替換<(…)
以使用從命令到 stdin 的輸入替換從文件到 stdin 的輸入。
當然,我們這里的示例令人困惑,因為這兩個變體具有相同的效果:
grep "hehe" < test.sh grep "hehe" < <(cat test.sh)
但從技術上講,來自文件的 stdin 輸入與來自從文件獲取輸入的命令輸出的 stdin 輸入仍然是一種不同的機制。
來源:高級 Bash 腳本手冊,關於進程替換的部分(從“其他一些用法”開始閱讀)。
它用作標准輸入。
嘗試:
grep "hehe" - $(cat test.sh)
那可能是錯誤的; 我不能在這台電腦上測試它。 如果像您嘗試的那樣在沒有管道的情況下執行此操作,grep 會將最后一個參數視為文件名,即查找名為 [test.sh 的內容] 的文件。 如果你給它傳遞一個 - (或者不放最后一個參數),你告訴它使用標准輸入作為文件。
你也可以只通過 grep 一個文件來掃描:
grep "hehe" test.sh
...但您似乎問了更多籠統的 bash 問題,而不是真正的 grep 用法問題,所以這可能沒有太大幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.