# 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. 别名