16.2. 代码块重定向

while, until, 和 for 循环代码块, 甚至 if/then 测试结构的代码块都能做到标准输入的重定向. 即使函数也可以使用这种重定向的格式 (参考例子 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文件中读而非在标准输入中读.
  20   echo $name
  21   let "count += 1"
  22 done <"$Filename"           # 重定向标准输入到文件$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有时仍会启用一个子shell来处理重定向的"while"循环.
  38 
  39 abc=hi
  40 echo -e "1\n2\n3" | while read l
  41      do abc="$l"
  42         echo $abc
  43      done
  44 echo $abc
  45 
  46 # (多谢Bruno de Oliveira Schneider给出上面的代码片段来演示此问题.)
  47 #


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

   1 #!/bin/bash
   2 
   3 # 这是前个脚本的另一个版本.
   4 
   5 #  Heiner Steven谈到
   6 #+ 在重定向循环时会以子Shell运行的环境里,
   7 #+ 循环内的值在循环结束后不会保持循环内的值.
   8 #
   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                 # 把标准输入关联到文件描述符.
  20 exec 0<"$Filename"        # 重定向标准输入.
  21 
  22 count=0
  23 echo
  24 
  25 
  26 while [ "$name" != Smith ]
  27 do
  28   read name               # 从标准输入($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                 # 恢复旧的标准输入.
  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中读而非从标准输入中读.
  15   echo $name
  16 done <"$Filename"             # 重定向标准输入到文件$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 #  代码非常的刻意和难看,但至少展示了for循环的标准输入可以重定向...
  14 #+ 当然你要足够聪明能看出来.
  15 #
  16 #
  17 # 更简洁的办法是     line_count=$(wc -l < "$Filename")
  18 
  19 
  20 for name in `seq $line_count`  # 调用 "seq" 来打印数字序列.
  21 # while [ "$name" != Smith ]   --   for循环比单个"while"循环更复杂   --
  22 do
  23   read name                    # 从$Filename文件而非标准输入读.
  24   echo $name
  25   if [ "$name" = Smith ]       # 因为用for循环,所以需要这个累赘测试.
  26   then
  27     break
  28   fi  
  29 done <"$Filename"              # 重定向标准输入到文件 $Filename. 
  30 #    ^^^^^^^^^^^^
  31 
  32 exit 0

我们也可以修改前面的例子使其能重定向循环的标准输出.


例子 16-9. 重定向 for 循环 (标准输入和标准输出都做重定向)

   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"     # 重定向标准输出到文件 $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 # "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".

重定向代码块的标准输出有保存它的输出到文件中的作用. 参考例子 3-2.

Here documents 是重定向代码块的一个特例.