Shell進程替換

2020-07-16 10:04:46
進程替換和命令替換非常相似。命令替換是把一個命令的輸出結果賦值給另一個變數,例如dir_files=`ls -l`date_time=$(date);而進程替換則是把一個命令的輸出結果傳遞給另一個(組)命令。

為了說明進程替換的必要性,我們先來看一個使用管道的例子:
echo "http://c.biancheng.net/shell/" | read
echo $REPLY
以上程式碼輸出結果總是為空,因為 echo 命令在父 Shell 中執行,而 read 命令在子 Shell 中執行,當 read 執行結束時,子 Shell 被銷毀,REPLY 變數也就消失了。管道中的命令總是在子 Shell 中執行的,任何給變數賦值的命令都會遭遇到這個問題。

使用 read 讀取資料時,如果沒有提供變數名,那麼讀取到的資料將存放到環境變數 REPLY 中,這一點已在《Shell read》中講到。

幸運的是,Shell 提供了一種“特異功能”,叫做進程替換,它可以用來解決這種麻煩。

Shell 進程替換有兩種寫法,一種用來產生標準輸出,借助輸入重定向,它的輸出結果可以作為另一個命令的輸入:

<(commands)

另一種用來接受標準輸入,藉助輸出重定向,它可以接收另一個命令的輸出結果:

>(commands)

commands 是一組命令列表,多個命令之間以分號;分隔。注意,<>與圓括號之間是沒有空格的。

例如,為了解決上面遇到的問題,我們可以像下面這樣使用進程替換:
read < <(echo "http://c.biancheng.net/shell/")
echo $REPLY
輸出結果:
http://c.biancheng.net/shell/

整體上來看,Shell 把echo "http://c.biancheng.net/shell/"的輸出結果作為 read 的輸入。<()用來捕獲 echo 命令的輸出結果,<用來將該結果重定向到 read。

注意,兩個<之間是有空格的,第一個<表示輸入重定向,第二個<()連在一起表示進程替換。

本例中的 read 命令和第二個 echo 命令都在當前 Shell 進程中執行,讀取的資料也會儲存到當前進程的 REPLY 變數,大家都在一個進程中,所以使用 echo 能夠成功輸出。

而在前面的例子中我們使用了管道,echo 命令在父進程中執行,read 命令在子進程中執行,讀取的資料也儲存在子進程的 REPLY 變數中,echo 命令和 REPLY 變數不在一個進程中,而子進程的環境變數對父進程是不可見的,所以讀取失敗。

再來看一個進程替換用作「接受標準輸入」的例子:

echo "C語言中文網" > >(read; echo "你好,$REPLY")

執行結果:
你好,C語言中文網

因為使用了重定向,read 命令從echo "C語言中文網"的輸出結果中讀取資料。

Shell進程替換的本質

為了能夠在不同進程之間傳遞資料,實際上進程替換會跟系統中的檔案關聯起來,這個檔案的名字為/dev/fd/n(n 是一個整數)。該檔案會作為引數傳遞給()中的命令,()中的命令對該檔案是讀取還是寫入取決於進程替換格式是<還是>
  • 如果是>(),那麼該檔案會給()中的命令提供輸入;藉助輸出重定向,要輸入的內容可以從其它命令而來。
  • 如果是<(),那麼該檔案會接收()中命令的輸出結果;藉助輸入重定向,可以將該檔案的內容作為其它命令的輸入。

使用 echo 命令可以檢視進程替換對應的檔名:
[c.biancheng.net]$ echo >(true)
/dev/fd/63
[c.biancheng.net]$ echo <(true)
/dev/fd/63
[c.biancheng.net]$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62
/dev/fd/目錄下有很多序號檔案,進程替換一般用的是 63 號檔案,該檔案是系統內部檔案,我們一般檢視不到。

我們通過下面的語句進行範例分析:

echo "shellscript" > >(read; echo "hello, $REPLY")

第一個>表示輸出重定向,它把第一個 echo 命令的輸出結果重定向到/dev/fd/63檔案中。

>()中的第一個命令是 read,它需要從標準輸入中讀取資料,此時就用/dev/fd/63作為輸入檔案,把該檔案的內容交給 read 命令,接著使用 echo 命令輸出 read 讀取到的內容。

可以看到,/dev/fd/63檔案起到了資料中轉或者資料橋樑的作用,借助重定向,它將>()內部的命令和外部的命令聯絡起來,使得資料能夠在這些命令之間流通。