20.2 重定向代码块

有如 while, until, 和 for 循环, 甚至 if/then 也可以重定向 标准输入 测试代码块. 甚至连一个函数都可以用这个方法进行重定向 (见 样例 24-11). 代码块的末尾部分的 “<” 就是用来完成这个的.

样例 20-5. while 循环的重定向

1. #!/bin/bash
2. # redir2.sh

4. if [ -z "$1" ]
5. then
6. Filename=names.data       # 如果不指定文件名的默认值.
7. else
8. Filename=$1
9. fi
10. #+ Filename=${1:-names.data}
11. #  can replace the above test (parameter substitution).

13. count=0

15. echo

17. while [ "$name" != Smith ]  # 为什么变量 "$name" 加引号?
18. do
19. read name                 # 从 $Filename 读取值, 而不是 标准输入.
20. echo $name
21. let "count += 1"
22. done <"$Filename"           # 重定向标准输入到文件 $Filename.
23. #    ^^^^^^^^^^^^

25. echo; echo "$count names read"; echo

27. exit 0

29. #  注意在一些老的脚本语言中,
30. #+ 循环的重定向会跑在子 shell 的环境中.
31. #  因此, $count 返回 0, 在循环外已经初始化过值.
32. #  Bash 和 ksh *只要可能* 会避免启动子 shell ,
33. #+ 所以这个脚本作为样例运行成功.
34. #  (感谢 Heiner Steven 指出这点.)

36. #  然而 . . .
37. #  Bash 有时候 *能* 在 "只读的 while" 循环启动子进程 ,
38. #+ 不同于 "while" 循环的重定向.

40. abc=hi
41. echo -e "1\n2\n3" | while read l
42. do abc="$l"
43. echo $abc
44. done
45. echo $abc

47. #  感谢, Bruno de Oliveira Schneider 上面的演示代码.
48. #  也感谢 Brian Onn 纠正了注释的错误.

样例 20-6. 另一种形式的 while 循环重定向

1. #!/bin/bash

3. # 这是之前的另一种形式的脚本.

5. #  Heiner Steven 提议在重定向循环时候运行在子 shell 可以作为一个变通方案
6. #+ 因此直到循环终止时循环内部的变量不需要保证他们的值

9. if [ -z "$1" ]
10. then
11. Filename=names.data     # 如果不指定文件名的默认值.
12. else
13. Filename=$1
14. fi

17. exec 3<&0                 # 保存标准输入到文件描述符 3.
18. exec 0<"$Filename"        # 重定向标准输入.

20. count=0
21. echo

24. while [ "$name" != Smith ]
25. do
26. read name               # 从重定向的标准输入($Filename)读取值.
27. echo $name
28. let "count += 1"
29. done                      #  从 $Filename 循环读
30. #+ 因为第 20 行.

32. #  这个脚本的早期版本在 "while" 循环 done <"$Filename" 终止
33. #  练习:
34. #  为什么这个没必要?

37. exec 0<&3                 # 恢复早前的标准输入.
38. exec 3<&-                 # 关闭临时的文件描述符 3.

40. echo; echo "$count names read"; echo

42. exit 0

样例 20-7. until 循环的重定向

1. #!/bin/bash
2. # 同先前的脚本一样, 不过用的是 "until" 循环.

4. if [ -z "$1" ]
5. then
6. Filename=names.data         # 如果不指定文件的默认值.
7. else
8. Filename=$1
9. fi

11. # while [ "$name" != Smith ]
12. until [ "$name" = Smith ]     # 变  !=  为 =.
13. do
14. read name                   # 从 $Filename 读取值, 而不是标准输入.
15. echo $name
16. done <"$Filename"             # 重定向标准输入到文件 "$Filename".
17. #    ^^^^^^^^^^^^

19. # 和之前的 "while" 循环样例相同的结果.

21. exit 0

样例 20-8. for 循环的重定向

2. #!/bin/bash

4. if [ -z "$1" ]
5. then
6. Filename=names.data          # 如果不指定文件的默认值.
7. else
8. Filename=$1
9. fi

11. line_count=`wc $Filename | awk '{ print $1 }'`
12. #           目标文件的行数.
13. #
14. #  非常作和不完善, 然而这只是证明 "for" 循环中的重定向标准输入是可行的
15. #+ 如果你足够聪明的话.
16. #
17. # 简介的做法是     line_count=$(wc -l < "$Filename")

20. for name in `seq $line_count`  # 回忆下 "seq" 可以输入数组序列.
21. # while [ "$name" != Smith ]   --   比 "while" 循环更复杂的循环   --
22. do
23. read name                    # 从 $Filename 读取值, 而不是标准输入.
24. echo $name
25. if [ "$name" = Smith ]       # 这需要所有这些额外的设置.
26. then
27. break
28. fi
29. done <"$Filename"              # 重定向标准输入到文件 "$Filename".
30. #    ^^^^^^^^^^^^

32. exit 0

我们可以修改先前的样例也可以重定向循环的输出.

样例 20-9. for 循环的重定向 (同时重定向标准输入和标准输出)

1. #!/bin/bash

3. if [ -z "$1" ]
4. then
5. Filename=names.data          # 如果不指定文件的默认值.
6. else
7. Filename=$1
8. fi

10. Savefile=$Filename.new         # 报错的结果的文件名.
11. FinalName=Jonah                # 停止 "read" 的终止字符.

13. line_count=`wc $Filename | awk '{ print $1 }'`  # 目标文件行数.

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"     # 重定向标准输入到文件 $Filename,
25. #    ^^^^^^^^^^^^^^^^^^^^^^^^^^^       并且报错结果到备份文件.

27. exit 0

样例 20-10. if/then test的重定向

1. #!/bin/bash

3. if [ -z "$1" ]
4. then
5. Filename=names.data   # 如果不指定文件的默认值.
6. else
7. Filename=$1
8. fi

10. TRUE=1

12. if [ "$TRUE" ]          # if true    和   if :   都可以工作.
13. then
14. read name
15. echo $name
16. fi <"$Filename"
17. #  ^^^^^^^^^^^^

19. # 只读取文件的首行.
20. # "if/then" test 除非嵌入在循环内部否则没办法迭代.

22. exit 0

样例 20-11. 上述样例的数据文件 names.data

2. Aristotle
3. Arrhenius
4. Belisarius
5. Capablanca
6. Dickens
7. Euler
8. Goethe
9. Hegel
10. Jonah
11. Laplace
12. Maroczy
13. Purcell
14. Schmidt
15. Schopenhauer
16. Semmelweiss
17. Smith
18. Steinmetz
19. Tukhashevsky
20. Turing
21. Venn
22. Warshawski
23. Znosko-Borowski

25. #+ 这是 "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh" 的数据文件.

代码块的标准输出的重定向影响了保存到文件的输出. 见样例 样例 3-2.

嵌入文档 是种特别的重定向代码块的方法. 既然如此,它使得在 while 循环的标准输入里传入嵌入文档的输出变得可能.

1. # 这个样例来自 Albert Siersema
2. # 得到了使用许可 (感谢!).

4. function doesOutput()
5. # 当然这也是个外部命令.
6. # 这里用函数进行演示会更好一点.
7. {
8. ls -al *.jpg | awk '{print $5,$9}'
9. }

12. nr=0          #  我们希望在 'while' 循环里可以操作这些
13. totalSize=0   #+ 并且在 'while' 循环结束时看到改变.

15. while read fileSize fileName ; do
16. echo "$fileName is $fileSize bytes"
17. let nr++
18. totalSize=$((totalSize+fileSize))   # Or: "let totalSize+=fileSize"
19. done<<EOF
20. $(doesOutput)
21. EOF

23. echo "$nr files totaling $totalSize bytes"