象while, until, 和for循环代码块, 甚至if/then测试结构的代码块, 都可以对stdin进行重定向. 即使函数也可以使用这种重定向方式(请参考例子 23-11). 要想做到这些, 都要依靠代码块结尾的<操作符.
例子 16-5. while循环的重定向
1 #!/bin/bash 2 # redir2.sh 3 4 if [ -z "$1" ] 5 then 6 Filename=names.data # 如果没有指定文件名, 则使用这个默认值. 7 else 8 Filename=$1 9 fi 10 #+ Filename=${1:-names.data} 11 # 这句可代替上面的测试(参数替换). 12 13 count=0 14 15 echo 16 17 while [ "$name" != Smith ] # 为什么变量$name要用引号? 18 do 19 read name # 从$Filename文件中读取输入, 而不是在stdin中读取输入. 20 echo $name 21 let "count += 1" 22 done <"$Filename" # 重定向stdin到文件$Filename. 23 # ^^^^^^^^^^^^ 24 25 echo; echo "$count names read"; echo 26 27 exit 0 28 29 # 注意在一些比较老的shell脚本编程语言中, 30 #+ 重定向的循环是放在子shell里运行的. 31 # 因此, $count 值返回后会是 0, 此值是在循环开始前的初始值. 32 # *如果可能的话*, 尽量避免在Bash或ksh中使用子shell, 33 #+ 所以这个脚本能够正确的运行. 34 # (多谢Heiner Steven指出这个问题.) 35 36 # 然而 . . . 37 # Bash有时还是*会*在一个使用管道的"while-read"循环中启动一个子shell, 38 #+ 与重定向的"while"循环还是有区别的. 39 40 abc=hi 41 echo -e "1\n2\n3" | while read l 42 do abc="$l" 43 echo $abc 44 done 45 echo $abc 46 47 # 感谢, Bruno de Oliveira Schneider 48 #+ 给出上面的代码片段来演示此问题. 49 # 同时, 感谢, Brian Onn, 修正了一个注释错误. |
例子 16-6. 重定向while循环的另一种形式
1 #!/bin/bash 2 3 # 这是上个脚本的另一个版本. 4 5 # Heiner Steven建议, 6 #+ 为了避免重定向循环运行在子shell中(老版本的shell会这么做), 最好让重定向循环运行在当前工作区内, 7 #+ 这样的话, 需要提前进行文件描述符重定向, 8 #+ 因为变量如果在(子shell上运行的)循环中被修改的话, 循环结束后并不会保存修改后的值. 9 10 11 if [ -z "$1" ] 12 then 13 Filename=names.data # 如果没有指定文件名则使用默认值. 14 else 15 Filename=$1 16 fi 17 18 19 exec 3<&0 # 将stdin保存到文件描述符3. 20 exec 0<"$Filename" # 重定向标准输入. 21 22 count=0 23 echo 24 25 26 while [ "$name" != Smith ] 27 do 28 read name # 从stdin(现在已经是$Filename了)中读取. 29 echo $name 30 let "count += 1" 31 done # 从文件$Filename中循环读取 32 #+ 因为文件(译者注:指默认文件, 在本节最后)有20行. 33 34 # 这个脚本原先在"while"循环的结尾还有一句: 35 #+ done <"$Filename" 36 # 练习: 37 # 为什么不需要这句了? 38 39 40 exec 0<&3 # 恢复保存的stdin. 41 exec 3<&- # 关闭临时文件描述符3. 42 43 echo; echo "$count names read"; echo 44 45 exit 0 |
例子 16-7. 重定向until循环
1 #!/bin/bash 2 # 和前面的例子相同, 但使用的是"until"循环. 3 4 if [ -z "$1" ] 5 then 6 Filename=names.data # 如果没有指定文件名那就使用默认值. 7 else 8 Filename=$1 9 fi 10 11 # while [ "$name" != Smith ] 12 until [ "$name" = Smith ] # 把!=改为=. 13 do 14 read name # 从$Filename中读取, 而不是从stdin中读取. 15 echo $name 16 done <"$Filename" # 重定向stdin到文件$Filename. 17 # ^^^^^^^^^^^^ 18 19 # 结果和前面例子的"while"循环相同. 20 21 exit 0 |
例子 16-8. 重定向for循环
1 #!/bin/bash 2 3 if [ -z "$1" ] 4 then 5 Filename=names.data # 如果没有指定文件名就使用默认值. 6 else 7 Filename=$1 8 fi 9 10 line_count=`wc $Filename | awk '{ print $1 }'` 11 # 目标文件的行数. 12 # 13 # 此处的代码太过做作, 并且写得很难看, 14 #+ 但至少展示了"for"循环的stdin可以重定向... 15 #+ 当然, 你得足够聪明, 才能看得出来. 16 # 17 # 更简洁的写法是 line_count=$(wc -l < "$Filename") 18 19 20 for name in `seq $line_count` # "seq"打印出数字序列. 21 # while [ "$name" != Smith ] -- 比"while"循环更复杂 -- 22 do 23 read name # 从$Filename中, 而非从stdin中读取. 24 echo $name 25 if [ "$name" = Smith ] # 因为用for循环, 所以需要这个多余测试. 26 then 27 break 28 fi 29 done <"$Filename" # 重定向stdin到文件$Filename. 30 # ^^^^^^^^^^^^ 31 32 exit 0 |
我们也可以修改前面的例子使其能重定向循环的标准输出.
例子 16-9. 重定向for循环(stdin和stdout都进行重定向)
1 #!/bin/bash 2 3 if [ -z "$1" ] 4 then 5 Filename=names.data # 如果没有指定文件名, 则使用默认值. 6 else 7 Filename=$1 8 fi 9 10 Savefile=$Filename.new # 保存最终结果的文件名. 11 FinalName=Jonah # 终止"read"时的名称. 12 13 line_count=`wc $Filename | awk '{ print $1 }'` # 目标文件的行数. 14 15 16 for name in `seq $line_count` 17 do 18 read name 19 echo "$name" 20 if [ "$name" = "$FinalName" ] 21 then 22 break 23 fi 24 done < "$Filename" > "$Savefile" # 重定向stdin到文件$Filename, 25 # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 并且将它保存到备份文件中. 26 27 exit 0 |
例子 16-10. 重定向if/then测试结构
1 #!/bin/bash 2 3 if [ -z "$1" ] 4 then 5 Filename=names.data # 如果文件名没有指定, 使用默认值. 6 else 7 Filename=$1 8 fi 9 10 TRUE=1 11 12 if [ "$TRUE" ] # if true 和 if : 都可以. 13 then 14 read name 15 echo $name 16 fi <"$Filename" 17 # ^^^^^^^^^^^^ 18 19 # 只读取了文件的第一行. 20 # An "if/then"测试结构不能自动地反复地执行, 除非把它们嵌到循环里. 21 22 exit 0 |
例子 16-11. 用于上面例子的"names.data"数据文件
1 Aristotle 2 Belisarius 3 Capablanca 4 Euler 5 Goethe 6 Hamurabi 7 Jonah 8 Laplace 9 Maroczy 10 Purcell 11 Schmidt 12 Semmelweiss 13 Smith 14 Turing 15 Venn 16 Wilson 17 Znosko-Borowski 18 19 # 此数据文件用于: 20 #+ "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh". |
重定向代码块的stdout, 与"将代码块的输出保存到文件中"具有相同的效果. 请参考例子 3-2.
here document 是重定向代码块的一个特例.