# 第二十三章. 進程替換
用[管道](http://tldp.org/LDP/abs/html/special-chars.html#PIPEREF) 將一個命令的 ```標準輸出``` 輸送到另一個命令的 ```標準輸入``` 是個強大的技術。但是如果你需要用管道輸送_多個_命令的 ```標準輸出``` 怎么辦?這時候 _進程替換_ 就派上用場了。
_進程替換_ 把一個(或多個)[進程](http://tldp.org/LDP/abs/html/special-chars.html#PROCESSREF) 的輸出送到另一個進程的 ```標準輸入```。
**樣板**
命令列表要用括號括起來
```
>(command_list)
<(command_list)
```
進程替換使用 ```/dev/fd/<n>``` 文件發送括號內進程的結果到另一個進程。[1]
<img src="http://tldp.org/LDP/abs/images/caution.gif">"<"或">"與括號之間沒有空格,加上空格或報錯。
```
bash$ echo >(true)
/dev/fd/63
bash$ echo <(true)
/dev/fd/63
bash$ echo >(true) <(true)
/dev/fd/63 /dev/fd/62
bash$ wc <(cat /usr/share/dict/linux.words)
483523 483523 4992010 /dev/fd/63
bash$ grep script /usr/share/dict/linux.words | wc
262 262 3601
bash$ wc <(grep script /usr/share/dict/linux.words)
262 262 3601 /dev/fd/63
```
<img src="http://tldp.org/LDP/abs/images/note.gif">Bash用兩個文件描述符創建管道,```--fIn 和 fOut--``` 。[true](http://tldp.org/LDP/abs/html/internal.html#TRUEREF) 的```標準輸入```連接 fOut(dup2(fOut, 0)),然后Bash 傳遞一個 ```/dev/fd/fIn``` 參數給 **echo** 。在不使用 ```/dev/fd/<n>``` 的系統里,Bash可以用臨時文件(感謝 S.C. 指出這點)。
進程替換可以比較兩個不同命令的輸出,或者同一個命令使用不同選項的輸出。
```
bash$ comm <(ls -l) <(ls -al)
total 12
-rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0
-rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2
-rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 t2.sh
total 20
drwxrwxrwx 2 bozo bozo 4096 Mar 10 18:10 .
drwx------ 72 bozo bozo 4096 Mar 10 17:58 ..
-rw-rw-r-- 1 bozo bozo 78 Mar 10 12:58 File0
-rw-rw-r-- 1 bozo bozo 42 Mar 10 12:58 File2
-rw-rw-r-- 1 bozo bozo 103 Mar 10 12:58 t2.sh
```
進程替換可以比較兩個目錄的內容——來檢查哪些文件在這個目錄而不在那個目錄。
```
diff <(ls $first_directory) <(ls $second_directory)
```
進程替換的一些其他用法:
```
read -a list < <( od -Ad -w24 -t u2 /dev/urandom )
# 從 /dev/urandom 讀取一個隨機數列表
#+ 用 "od" 處理
#+ 輸送到 "read" 的標準輸入. . .
# 來自 "insertion-sort.bash" 示例腳本。
# 致謝:JuanJo Ciarlante。
```
```
PORT=6881 # bittorrent(BT端口)
# 掃描端口,確保沒有惡意行為
netcat -l $PORT | tee>(md5sum ->mydata-orig.md5) |
gzip | tee>(md5sum - | sed 's/-$/mydata.lz2/'>mydata-gz.md5)>mydata.gz
# 檢查解壓縮結果:
gzip -d<mydata.gz | md5sum -c mydata-orig.md5)
# 對原件的MD5校驗用來檢查標準輸入,并且探測壓縮當中出現的問題。
# Bill Davidsen 貢獻了這個例子
#+ (ABS指南作者做了輕微修改)。
```
```
cat <(ls -l)
# 等價于 ls -l | cat
sort -k 9 <(ls -l /bin) <(ls -l /usr/bin) <(ls -l /usr/X11R6/bin)
# 列出 3 個主要 'bin' 目錄的文件,按照文件名排序。
# 注意,有三個(數一下)單獨的命令輸送給了 'sort'。
diff <(command1) <(command2) # 比較命令輸出結果的不同之處。
tar cf >(bzip2 -c > file.tar.bz2) $directory_name
# 調用 "tar cf /dev/fd/?? $directory_name",然后 "bzip2 -c > file.tar.bz2"。
#
# 因為 /dev/fd/<n> 系統特性
# 不需要在兩個命令之間使用管道符
#
# 這個可以模擬
#
bzip2 -c < pipe > file.tar.bz2&
tar cf pipe $directory_name
rm pipe
# 或者
exec 3>&1
tar cf /dev/fd/4 $directory_name 4>&1 >&3 3>&- | bzip2 -c > file.tar.bz2 3>&-
exec 3>&-
# 致謝:Stéphane Chazelas
```
在子shell中 [echo 命令用管道輸送給 while-read 循環](http://tldp.org/LDP/abs/html/gotchas.html#BADREAD0)時會出現問題,下面是避免的方法:
**例23-1 不用 fork 的代碼塊重定向。**
```
#!/bin/bash
# wr-ps.bash: 使用進程替換的 while-read 循環。
# 示例由 Tomas Pospisek 貢獻。
# (ABS指南作者做了大量改動。)
echo
echo "random input" | while read i
do
global=3D": Not available outside the loop."
# ... 因為在子 shell 中運行。
done
echo "\$global (從子進程之外) = $global"
# $global (從子進程之外) =
echo; echo "--"; echo
while read i
do
echo $i
global=3D": Available outside the loop."
# ... 因為沒有在子 shell 中運行。
done < <( echo "random input" )
# ^ ^
echo "\$global (使用進程替換) = $global"
# 隨機輸入
# $global (使用進程替換)= 3D: Available outside the loop.
echo; echo "##########"; echo
# 同樣道理 . . .
declare -a inloop
index=0
cat $0 | while read line
do
inloop[$index]="$line"
((index++))
# 在子 shell 中運行,所以 ...
done
echo "OUTPUT = "
echo ${inloop[*]} # ... 什么也沒有顯示。
echo; echo "--"; echo
declare -a outloop
index=0
while read line
do
outloop[$index]="$line"
((index++))
# 沒有在子 shell 中運行,所以 ...
done < <( cat $0 )
echo "OUTPUT = "
echo ${outloop[*]} # ... 整個腳本的結果顯示出來。
exit $?
```
下面是個類似的例子。
**例 23-2. 重定向進程替換的輸出到一個循環內**
```
#!/bin/bash
# psub.bash
# 受 Diego Molina 啟發(感謝!)。
declare -a array0
while read
do
array0[${#array0[@]}]="$REPLY"
done < <( sed -e 's/bash/CRASH-BANG!/' $0 | grep bin | awk '{print $1}' )
# 由進程替換來設置'read'默認變量($REPLY)。
#+ 然后將變量復制到一個數組。
echo "${array0[@]}"
exit $?
# ====================================== #
# 運行結果:
bash psub.bash
#!/bin/CRASH-BANG! done #!/bin/CRASH-BANG!
```
一個讀者發來一個有趣的進程替換例子,如下:
```
# SuSE 發行版中提取的腳本片段:
# --------------------------------------------------------------#
while read des what mask iface; do
# 一些命令 ...
done < <(route -n)
# ^ ^ 第一個 < 是重定向,第二個是進程替換。
# 為了測試,我們讓它來做點兒事情。
while read des what mask iface; do
echo $des $what $mask $iface
done < <(route -n)
# 輸出內容:
# Kernel IP routing table
# Destination Gateway Genmask Flags Metric Ref Use Iface
# 127.0.0.0 0.0.0.0 255.0.0.0 U 0 0 0 lo
# --------------------------------------------------------------#
# 正如 Stéphane Chazelas 指出的,
#+ 一個更容易理解的等價代碼如下:
route -n |
while read des what mask iface; do # 通過管道輸出設置的變量
echo $des $what $mask $iface
done # 這段代碼的結果更上面的相同。
# 但是,Ulrich Gayer 指出 . . .
#+ 這段簡化版等價代碼在 while 循環里用了子 shell,
#+ 因此當管道終止時變量都消失了。
# --------------------------------------------------------------#
# 然而,Filip Moritz 說上面的兩個例子有一個微妙的區別,
#+ 見下面的代碼
(
route -n | while read x; do ((y++)); done
echo $y # $y is still unset
while read x; do ((y++)); done < <(route -n)
echo $y # $y has the number of lines of output of route -n
)
# 更通俗地說(譯者注:原文本行少了注釋符)
(
: | x=x
# 似乎啟動了子 shell ,就像
: | ( x=x )
# 而
x=x < <(:)
# 并沒有。
)
# 這個方法在解析 csv 和類似格式時很有用。
# 也就是在效果上,原始 SuSE 系統的代碼片段就是做這個用的。
```
注解 [1]
這個與命名管道(使用臨時文件)的效果相同,而且事實上,進程替換也曾經用過命名管道。
- 第一部分 初見shell
- 1. 為什么使用shell編程
- 2. 和Sha-Bang(#!)一起出發
- 2.1 調用一個腳本
- 2.2 牛刀小試
- 第二部分 shell基礎
- 3. 特殊字符
- 4. 變量與參數
- 4.1 變量替換
- 4.2 變量賦值
- 4.3 Bash弱類型變量
- 4.4 特殊變量類型
- 5. 引用
- 5.1 引用變量
- 5.2 轉義
- 6. 退出與退出狀態
- 7. 測試
- 7.1 測試結構
- 7.2 文件測試操作
- 7.3 其他比較操作
- 7.4 嵌套 if/then 條件測試
- 7.5 牛刀小試
- 8. 運算符相關話題
- 8.1 運算符
- 8.2 數字常量
- 8.3 雙圓括號結構
- 8.4 運算符優先級
- 第三部分 shell進階
- 10. 變量處理
- 10.1 字符串處理
- 10.1.1 使用 awk 處理字符串
- 10.1.2 參考資料
- 10.2 參數替換
- 11. 循環與分支
- 11.1 循環
- 11.2 嵌套循環
- 11.3 循環控制
- 11.4 測試與分支
- 12. 命令替換
- 13. 算術擴展
- 14. 休息時間
- 第五部分 進階話題
- 19. 嵌入文檔
- 20. I/O 重定向
- 20.1 使用 exec
- 20.2 重定向代碼塊
- 20.3 應用程序
- 22. 限制模式的Shell
- 23. 進程替換
- 26. 列表結構
- 25. 別名