16.1. 使用exec

exec <filename命令会将stdin重定向到文件中. 从这句开始, 所有的stdin就都来自于这个文件了, 而不是标准输入(通常都是键盘输入). 这样就提供了一种按行读取文件的方法, 并且可以使用sed和/或awk来对每一行进行分析.


例子 16-1. 使用exec重定向stdin

  1 #!/bin/bash
  2 # 使用'exec'重定向stdin. 
  3 
  4 
  5 exec 6<&0          # 将文件描述符#6与stdin链接起来. 
  6                    # 保存stdin. 
  7 
  8 exec < data-file   # stdin被文件"data-file"所代替. 
  9 
 10 read a1            # 读取文件"data-file"的第一行. 
 11 read a2            # 读取文件"data-file"的第二行. 
 12 
 13 echo
 14 echo "Following lines read from file."
 15 echo "-------------------------------"
 16 echo $a1
 17 echo $a2
 18 
 19 echo; echo; echo
 20 
 21 exec 0<&6 6<&-
 22 #  现在将stdin从fd #6中恢复, 因为刚才我们把stdin重定向到#6了, 
 23 #+ 然后关闭fd #6 ( 6<&- ), 好让这个描述符继续被其他进程所使用. 
 24 #
 25 # <&6 6<&-    这么做也可以. 
 26 
 27 echo -n "Enter data  "
 28 read b1  # 现在"read"已经恢复正常了, 就是能够正常的从stdin中读取.
 29 echo "Input read from stdin."
 30 echo "----------------------"
 31 echo "b1 = $b1"
 32 
 33 echo
 34 
 35 exit 0

同样的, exec >filename命令将会把stdout重定向到一个指定的文件中. 这样所有命令的输出就都会发送到那个指定的文件, 而不是stdout.

Important

exec N > filename会影响整个脚本或当前shell. 对于这个指定PID的脚本或shell来说, 从这句命令执行之后, 就会重定向到这个文件中, 然而 . . .

N > filename只会影响新fork出来的进程, 而不会影响整个脚本或shell. not the entire script or shell.

感谢你, Ahmed Darwish, 指出这个问题.


例子 16-2. 使用exec来重定向stdout

  1 #!/bin/bash
  2 # reassign-stdout.sh
  3 
  4 LOGFILE=logfile.txt
  5 
  6 exec 6>&1           # 将fd #6与stdout链接起来. 
  7                     # 保存stdout. 
  8 
  9 exec > $LOGFILE     # stdout就被文件"logfile.txt"所代替了. 
 10 
 11 # ----------------------------------------------------------- #
 12 # 在这块中所有命令的输出都会发送到文件$LOGFILE中. 
 13 
 14 echo -n "Logfile: "
 15 date
 16 echo "-------------------------------------"
 17 echo
 18 
 19 echo "Output of \"ls -al\" command"
 20 echo
 21 ls -al
 22 echo; echo
 23 echo "Output of \"df\" command"
 24 echo
 25 df
 26 
 27 # ----------------------------------------------------------- #
 28 
 29 exec 1>&6 6>&-      # 恢复stdout, 然后关闭文件描述符#6. 
 30 
 31 echo
 32 echo "== stdout now restored to default == "
 33 echo
 34 ls -al
 35 echo
 36 
 37 exit 0


例子 16-3. 使用exec在同一个脚本中重定向stdinstdout

  1 #!/bin/bash
  2 # upperconv.sh
  3 # 将一个指定的输入文件转换为大写. 
  4 
  5 E_FILE_ACCESS=70
  6 E_WRONG_ARGS=71
  7 
  8 if [ ! -r "$1" ]     # 判断指定的输入文件是否可读? 
  9 then
 10   echo "Can't read from input file!"
 11   echo "Usage: $0 input-file output-file"
 12   exit $E_FILE_ACCESS
 13 fi                   #  即使输入文件($1)没被指定
 14                      #+ 也还是会以相同的错误退出(为什么?). 
 15 
 16 if [ -z "$2" ]
 17 then
 18   echo "Need to specify output file."
 19   echo "Usage: $0 input-file output-file"
 20   exit $E_WRONG_ARGS
 21 fi
 22 
 23 
 24 exec 4<&0
 25 exec < $1            # 将会从输入文件中读取. 
 26 
 27 exec 7>&1
 28 exec > $2            # 将写到输出文件中. 
 29                      # 假设输出文件是可写的(添加检查?). 
 30 
 31 # -----------------------------------------------
 32     cat - | tr a-z A-Z   # 转换为大写. 
 33 #   ^^^^^                # 从stdin中读取. 
 34 #           ^^^^^^^^^^   # 写到stdout上. 
 35 # 然而, stdin和stdout都被重定向了. 
 36 # -----------------------------------------------
 37 
 38 exec 1>&7 7>&-       # 恢复stout.
 39 exec 0<&4 4<&-       # 恢复stdin.
 40 
 41 # 恢复之后, 下边这行代码将会如预期的一样打印到stdout上. 
 42 echo "File \"$1\" written to \"$2\" as uppercase conversion."
 43 
 44 exit 0

I/O重定向是一种避免可怕的子shell中不可访问变量问题的方法.


例子 16-4. 避免子shell

  1 #!/bin/bash
  2 # avoid-subshell.sh
  3 # 由Matthew Walker所提出的建议. 
  4 
  5 Lines=0
  6 
  7 echo
  8 
  9 cat myfile.txt | while read line;  #  (译者注: 管道会产生子shell)
 10                  do {
 11                    echo $line
 12                    (( Lines++ ));  #  增加这个变量的值
 13                                    #+ 但是外部循环却不能访问. 
 14                                    #  子shell问题. 
 15                  }
 16                  done
 17 
 18 echo "Number of lines read = $Lines"     # 0
 19                                          # 错误!
 20 
 21 echo "------------------------"
 22 
 23 
 24 exec 3<> myfile.txt
 25 while read line <&3
 26 do {
 27   echo "$line"
 28   (( Lines++ ));                   #  增加这个变量的值
 29                                    #+ 现在外部循环就可以访问了. 
 30                                    #  没有子shell, 现在就没问题了. 
 31 }
 32 done
 33 exec 3>&-
 34 
 35 echo "Number of lines read = $Lines"     # 8
 36 
 37 echo
 38 
 39 exit 0
 40 
 41 # 下边这些行是这个脚本的结果, 脚本是不会走到这里的. 
 42 
 43 $ cat myfile.txt
 44 
 45 Line 1.
 46 Line 2.
 47 Line 3.
 48 Line 4.
 49 Line 5.
 50 Line 6.
 51 Line 7.
 52 Line 8.