# 20.2 重定向代碼塊
有如 [while](http://tldp.org/LDP/abs/html/loops1.html#WHILELOOPREF), [until](http://tldp.org/LDP/abs/html/loops1.html#FORLOOPREF1), 和 [for](http://tldp.org/LDP/abs/html/loops1.html#UNTILLOOPREF) 循環, 甚至 [if/then](http://tldp.org/LDP/abs/html/tests.html#IFTHEN) 也可以重定向 標準輸入 測試代碼塊. 甚至連一個函數都可以用這個方法進行重定向 (見 [樣例 24-11](http://tldp.org/LDP/abs/html/complexfunct.html#REALNAME)). 代碼塊的末尾部分的 "<" 就是用來完成這個的.
樣例 20-5. while 循環的重定向
```
#!/bin/bash
# redir2.sh
if [ -z "$1" ]
then
Filename=names.data # 如果不指定文件名的默認值.
else
Filename=$1
fi
#+ Filename=${1:-names.data}
# can replace the above test (parameter substitution).
count=0
echo
while [ "$name" != Smith ] # 為什么變量 "$name" 加引號?
do
read name # 從 $Filename 讀取值, 而不是 標準輸入.
echo $name
let "count += 1"
done <"$Filename" # 重定向標準輸入到文件 $Filename.
# ^^^^^^^^^^^^
echo; echo "$count names read"; echo
exit 0
# 注意在一些老的腳本語言中,
#+ 循環的重定向會跑在子 shell 的環境中.
# 因此, $count 返回 0, 在循環外已經初始化過值.
# Bash 和 ksh *只要可能* 會避免啟動子 shell ,
#+ 所以這個腳本作為樣例運行成功.
# (感謝 Heiner Steven 指出這點.)
# 然而 . . .
# Bash 有時候 *能* 在 "只讀的 while" 循環啟動子進程 ,
#+ 不同于 "while" 循環的重定向.
abc=hi
echo -e "1\n2\n3" | while read l
do abc="$l"
echo $abc
done
echo $abc
# 感謝, Bruno de Oliveira Schneider 上面的演示代碼.
# 也感謝 Brian Onn 糾正了注釋的錯誤.
```
樣例 20-6. 另一種形式的 while 循環重定向
```
#!/bin/bash
# 這是之前的另一種形式的腳本.
# Heiner Steven 提議在重定向循環時候運行在子 shell 可以作為一個變通方案
#+ 因此直到循環終止時循環內部的變量不需要保證他們的值
if [ -z "$1" ]
then
Filename=names.data # 如果不指定文件名的默認值.
else
Filename=$1
fi
exec 3<&0 # 保存標準輸入到文件描述符 3.
exec 0<"$Filename" # 重定向標準輸入.
count=0
echo
while [ "$name" != Smith ]
do
read name # 從重定向的標準輸入($Filename)讀取值.
echo $name
let "count += 1"
done # 從 $Filename 循環讀
#+ 因為第 20 行.
# 這個腳本的早期版本在 "while" 循環 done <"$Filename" 終止
# 練習:
# 為什么這個沒必要?
exec 0<&3 # 恢復早前的標準輸入.
exec 3<&- # 關閉臨時的文件描述符 3.
echo; echo "$count names read"; echo
exit 0
```
樣例 20-7. until 循環的重定向
```
#!/bin/bash
# 同先前的腳本一樣, 不過用的是 "until" 循環.
if [ -z "$1" ]
then
Filename=names.data # 如果不指定文件的默認值.
else
Filename=$1
fi
# while [ "$name" != Smith ]
until [ "$name" = Smith ] # 變 != 為 =.
do
read name # 從 $Filename 讀取值, 而不是標準輸入.
echo $name
done <"$Filename" # 重定向標準輸入到文件 "$Filename".
# ^^^^^^^^^^^^
# 和之前的 "while" 循環樣例相同的結果.
exit 0
```
樣例 20-8. for 循環的重定向
```
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # 如果不指定文件的默認值.
else
Filename=$1
fi
line_count=`wc $Filename | awk '{ print $1 }'`
# 目標文件的行數.
#
# 非常作和不完善, 然而這只是證明 "for" 循環中的重定向標準輸入是可行的
#+ 如果你足夠聰明的話.
#
# 簡介的做法是 line_count=$(wc -l < "$Filename")
for name in `seq $line_count` # 回憶下 "seq" 可以輸入數組序列.
# while [ "$name" != Smith ] -- 比 "while" 循環更復雜的循環 --
do
read name # 從 $Filename 讀取值, 而不是標準輸入.
echo $name
if [ "$name" = Smith ] # 這需要所有這些額外的設置.
then
break
fi
done <"$Filename" # 重定向標準輸入到文件 "$Filename".
# ^^^^^^^^^^^^
exit 0
```
我們可以修改先前的樣例也可以重定向循環的輸出.
樣例 20-9. for 循環的重定向 (同時重定向標準輸入和標準輸出)
```
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # 如果不指定文件的默認值.
else
Filename=$1
fi
Savefile=$Filename.new # 報錯的結果的文件名.
FinalName=Jonah # 停止 "read" 的終止字符.
line_count=`wc $Filename | awk '{ print $1 }'` # 目標文件行數.
for name in `seq $line_count`
do
read name
echo "$name"
if [ "$name" = "$FinalName" ]
then
break
fi
done < "$Filename" > "$Savefile" # 重定向標準輸入到文件 $Filename,
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 并且報錯結果到備份文件.
exit 0
```
樣例 20-10. if/then test的重定向
```
#!/bin/bash
if [ -z "$1" ]
then
Filename=names.data # 如果不指定文件的默認值.
else
Filename=$1
fi
TRUE=1
if [ "$TRUE" ] # if true 和 if : 都可以工作.
then
read name
echo $name
fi <"$Filename"
# ^^^^^^^^^^^^
# 只讀取文件的首行.
# "if/then" test 除非嵌入在循環內部否則沒辦法迭代.
exit 0
```
樣例 20-11. 上述樣例的數據文件 names.data
```
Aristotle
Arrhenius
Belisarius
Capablanca
Dickens
Euler
Goethe
Hegel
Jonah
Laplace
Maroczy
Purcell
Schmidt
Schopenhauer
Semmelweiss
Smith
Steinmetz
Tukhashevsky
Turing
Venn
Warshawski
Znosko-Borowski
#+ 這是 "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh" 的數據文件.
```
代碼塊的標準輸出的重定向影響了保存到文件的輸出. 見樣例 [樣例 3-2](http://tldp.org/LDP/abs/html/special-chars.html#RPMCHECK).
[嵌入文檔](http://tldp.org/LDP/abs/html/here-docs.html#HEREDOCREF) 是種特別的重定向代碼塊的方法. 既然如此,它使得在 while 循環的標準輸入里傳入嵌入文檔的輸出變得可能.
```
# 這個樣例來自 Albert Siersema
# 得到了使用許可 (感謝!).
function doesOutput()
# 當然這也是個外部命令.
# 這里用函數進行演示會更好一點.
{
ls -al *.jpg | awk '{print $5,$9}'
}
nr=0 # 我們希望在 'while' 循環里可以操作這些
totalSize=0 #+ 并且在 'while' 循環結束時看到改變.
while read fileSize fileName ; do
echo "$fileName is $fileSize bytes"
let nr++
totalSize=$((totalSize+fileSize)) # Or: "let totalSize+=fileSize"
done<<EOF
$(doesOutput)
EOF
echo "$nr files totaling $totalSize bytes"
```
- 第一部分 初見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. 別名