20.2 重定向代码块

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

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

  1. #!/bin/bash
  2. # redir2.sh
  3. if [ -z "$1" ]
  4. then
  5. Filename=names.data # 如果不指定文件名的默认值.
  6. else
  7. Filename=$1
  8. fi
  9. #+ Filename=${1:-names.data}
  10. # can replace the above test (parameter substitution).
  11. count=0
  12. echo
  13. while [ "$name" != Smith ] # 为什么变量 "$name" 加引号?
  14. do
  15. read name # 从 $Filename 读取值, 而不是 标准输入.
  16. echo $name
  17. let "count += 1"
  18. done <"$Filename" # 重定向标准输入到文件 $Filename.
  19. # ^^^^^^^^^^^^
  20. echo; echo "$count names read"; echo
  21. exit 0
  22. # 注意在一些老的脚本语言中,
  23. #+ 循环的重定向会跑在子 shell 的环境中.
  24. # 因此, $count 返回 0, 在循环外已经初始化过值.
  25. # Bash 和 ksh *只要可能* 会避免启动子 shell ,
  26. #+ 所以这个脚本作为样例运行成功.
  27. # (感谢 Heiner Steven 指出这点.)
  28. # 然而 . . .
  29. # Bash 有时候 *能* 在 "只读的 while" 循环启动子进程 ,
  30. #+ 不同于 "while" 循环的重定向.
  31. abc=hi
  32. echo -e "1\n2\n3" | while read l
  33. do abc="$l"
  34. echo $abc
  35. done
  36. echo $abc
  37. # 感谢, Bruno de Oliveira Schneider 上面的演示代码.
  38. # 也感谢 Brian Onn 纠正了注释的错误.

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

  1. #!/bin/bash
  2. # 这是之前的另一种形式的脚本.
  3. # Heiner Steven 提议在重定向循环时候运行在子 shell 可以作为一个变通方案
  4. #+ 因此直到循环终止时循环内部的变量不需要保证他们的值
  5. if [ -z "$1" ]
  6. then
  7. Filename=names.data # 如果不指定文件名的默认值.
  8. else
  9. Filename=$1
  10. fi
  11. exec 3<&0 # 保存标准输入到文件描述符 3.
  12. exec 0<"$Filename" # 重定向标准输入.
  13. count=0
  14. echo
  15. while [ "$name" != Smith ]
  16. do
  17. read name # 从重定向的标准输入($Filename)读取值.
  18. echo $name
  19. let "count += 1"
  20. done # 从 $Filename 循环读
  21. #+ 因为第 20 行.
  22. # 这个脚本的早期版本在 "while" 循环 done <"$Filename" 终止
  23. # 练习:
  24. # 为什么这个没必要?
  25. exec 0<&3 # 恢复早前的标准输入.
  26. exec 3<&- # 关闭临时的文件描述符 3.
  27. echo; echo "$count names read"; echo
  28. exit 0

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

  1. #!/bin/bash
  2. # 同先前的脚本一样, 不过用的是 "until" 循环.
  3. if [ -z "$1" ]
  4. then
  5. Filename=names.data # 如果不指定文件的默认值.
  6. else
  7. Filename=$1
  8. fi
  9. # while [ "$name" != Smith ]
  10. until [ "$name" = Smith ] # 变 != 为 =.
  11. do
  12. read name # 从 $Filename 读取值, 而不是标准输入.
  13. echo $name
  14. done <"$Filename" # 重定向标准输入到文件 "$Filename".
  15. # ^^^^^^^^^^^^
  16. # 和之前的 "while" 循环样例相同的结果.
  17. exit 0

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

  1. #!/bin/bash
  2. if [ -z "$1" ]
  3. then
  4. Filename=names.data # 如果不指定文件的默认值.
  5. else
  6. Filename=$1
  7. fi
  8. line_count=`wc $Filename | awk '{ print $1 }'`
  9. # 目标文件的行数.
  10. #
  11. # 非常作和不完善, 然而这只是证明 "for" 循环中的重定向标准输入是可行的
  12. #+ 如果你足够聪明的话.
  13. #
  14. # 简介的做法是 line_count=$(wc -l < "$Filename")
  15. for name in `seq $line_count` # 回忆下 "seq" 可以输入数组序列.
  16. # while [ "$name" != Smith ] -- 比 "while" 循环更复杂的循环 --
  17. do
  18. read name # 从 $Filename 读取值, 而不是标准输入.
  19. echo $name
  20. if [ "$name" = Smith ] # 这需要所有这些额外的设置.
  21. then
  22. break
  23. fi
  24. done <"$Filename" # 重定向标准输入到文件 "$Filename".
  25. # ^^^^^^^^^^^^
  26. exit 0

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

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

  1. #!/bin/bash
  2. if [ -z "$1" ]
  3. then
  4. Filename=names.data # 如果不指定文件的默认值.
  5. else
  6. Filename=$1
  7. fi
  8. Savefile=$Filename.new # 报错的结果的文件名.
  9. FinalName=Jonah # 停止 "read" 的终止字符.
  10. line_count=`wc $Filename | awk '{ print $1 }'` # 目标文件行数.
  11. for name in `seq $line_count`
  12. do
  13. read name
  14. echo "$name"
  15. if [ "$name" = "$FinalName" ]
  16. then
  17. break
  18. fi
  19. done < "$Filename" > "$Savefile" # 重定向标准输入到文件 $Filename,
  20. # ^^^^^^^^^^^^^^^^^^^^^^^^^^^ 并且报错结果到备份文件.
  21. exit 0

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

  1. #!/bin/bash
  2. if [ -z "$1" ]
  3. then
  4. Filename=names.data # 如果不指定文件的默认值.
  5. else
  6. Filename=$1
  7. fi
  8. TRUE=1
  9. if [ "$TRUE" ] # if true 和 if : 都可以工作.
  10. then
  11. read name
  12. echo $name
  13. fi <"$Filename"
  14. # ^^^^^^^^^^^^
  15. # 只读取文件的首行.
  16. # "if/then" test 除非嵌入在循环内部否则没办法迭代.
  17. exit 0

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

  1. Aristotle
  2. Arrhenius
  3. Belisarius
  4. Capablanca
  5. Dickens
  6. Euler
  7. Goethe
  8. Hegel
  9. Jonah
  10. Laplace
  11. Maroczy
  12. Purcell
  13. Schmidt
  14. Schopenhauer
  15. Semmelweiss
  16. Smith
  17. Steinmetz
  18. Tukhashevsky
  19. Turing
  20. Venn
  21. Warshawski
  22. Znosko-Borowski
  23. #+ 这是 "redir2.sh", "redir3.sh", "redir4.sh", "redir4a.sh", "redir5.sh" 的数据文件.

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

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

  1. # 这个样例来自 Albert Siersema
  2. # 得到了使用许可 (感谢!).
  3. function doesOutput()
  4. # 当然这也是个外部命令.
  5. # 这里用函数进行演示会更好一点.
  6. {
  7. ls -al *.jpg | awk '{print $5,$9}'
  8. }
  9. nr=0 # 我们希望在 'while' 循环里可以操作这些
  10. totalSize=0 #+ 并且在 'while' 循环结束时看到改变.
  11. while read fileSize fileName ; do
  12. echo "$fileName is $fileSize bytes"
  13. let nr++
  14. totalSize=$((totalSize+fileSize)) # Or: "let totalSize+=fileSize"
  15. done<<EOF
  16. $(doesOutput)
  17. EOF
  18. echo "$nr files totaling $totalSize bytes"