20.1 使用 exec

一个 exec < filename 命令重定向了 标准输入 到一个文件。自此所有 标准输入 都来自该文件而不是默认来源(通常是键盘输入)。在使用 sedawk 时候这种方式可以逐行读文件并逐行解析。

样例 20-1. 使用 exec 重定向 标准输入

  1. #!/bin/bash
  2. # 使用 'exec' 重定向 标准输入 .
  3. exec 6<&0 # 链接文件描述符 #6 到标准输入.
  4. # .
  5. exec < data-file # 标准输入被文件 "data-file" 替换
  6. read a1 # 读取文件 "data-file" 首行.
  7. read a2 # 读取文件 "data-file" 第二行
  8. echo
  9. echo "Following lines read from file."
  10. echo "-------------------------------"
  11. echo $a1
  12. echo $a2
  13. echo; echo; echo
  14. exec 0<&6 6<&-
  15. # 现在在之前保存的位置将从文件描述符 #6 将 标准输出 恢复.
  16. #+ 且关闭文件描述符 #6 ( 6<&- ) 让其他程序正常使用.
  17. #
  18. # <&6 6<&- also works.
  19. echo -n "Enter data "
  20. read b1 # 现在按预期的,从正常的标准输入 "read".
  21. echo "Input read from stdin."
  22. echo "----------------------"
  23. echo "b1 = $b1"
  24. echo
  25. exit 0

同理, exec >filename 重定向 标准输出 到指定文件. 他将所有的命令输出通常是 标准输出 重定向到指定的位置.

exec N > filename 影响整个脚本或当前 shell。PID 从重定向脚本或 shell 的那时候已经发生了改变. 然而 N > filename 影响的就是新派生的进程,而不是整个脚本或 shell。

样例 20-2. 使用 exec 重定向标准输出

  1. #!/bin/bash
  2. # reassign-stdout.sh
  3. LOGFILE=logfile.txt
  4. exec 6>&1 # 链接文件描述符 #6 到标准输出.
  5. # 保存标准输出.
  6. exec > $LOGFILE # 标准输出被文件 "logfile.txt" 替换.
  7. # ----------------------------------------------------------- #
  8. # 所有在这个块里的命令的输出都会发送到文件 $LOGFILE.
  9. echo -n "Logfile: "
  10. date
  11. echo "-------------------------------------"
  12. echo
  13. echo "Output of \"ls -al\" command"
  14. echo
  15. ls -al
  16. echo; echo
  17. echo "Output of \"df\" command"
  18. echo
  19. df
  20. # ----------------------------------------------------------- #
  21. exec 1>&6 6>&- # 关闭文件描述符 #6 恢复 标准输出.
  22. echo
  23. echo "== stdout now restored to default == "
  24. echo
  25. ls -al
  26. echo
  27. exit 0

样例 20-3. 用 exec 在一个脚本里同时重定向 标准输入 和 标准输出

  1. #!/bin/bash
  2. # upperconv.sh
  3. # 转化指定的输入文件成大写.
  4. E_FILE_ACCESS=70
  5. E_WRONG_ARGS=71
  6. if [ ! -r "$1" ] # 指定的输入文件是否可读?
  7. then
  8. echo "Can't read from input file!"
  9. echo "Usage: $0 input-file output-file"
  10. exit $E_FILE_ACCESS
  11. fi # 同样的错误退出
  12. #+ 等同如果输入文件 ($1) 未指定 (为什么?).
  13. if [ -z "$2" ]
  14. then
  15. echo "Need to specify output file."
  16. echo "Usage: $0 input-file output-file"
  17. exit $E_WRONG_ARGS
  18. fi
  19. exec 4<&0
  20. exec < $1 # 将从输入文件读取.
  21. exec 7>&1
  22. exec > $2 # 将写入输出文件.
  23. # 假定输出文件可写 (增加检测?).
  24. # -----------------------------------------------
  25. cat - | tr a-z A-Z # 转化大写.
  26. # ^^^^^ # 读取标准输入.
  27. # ^^^^^^^^^^ # 写到标准输出.
  28. # 然而标准输入和标准输出都会被重定向.
  29. # 注意 'cat' 可能会被遗漏.
  30. # -----------------------------------------------
  31. exec 1>&7 7>&- # 恢复标准输出.
  32. exec 0<&4 4<&- # 恢复标准输入.
  33. # 恢复后, 下面这行会预期从标准输出打印.
  34. echo "File \"$1\" written to \"$2\" as uppercase conversion."
  35. exit 0

I/O 重定向是种明智的规避 inaccessible variables within a subshell 问题的方法.

样例 20-4. 规避子 shell

  1. #!/bin/bash
  2. # avoid-subshell.sh
  3. # Matthew Walker 的建议.
  4. Lines=0
  5. echo
  6. cat myfile.txt | while read line;
  7. do {
  8. echo $line
  9. (( Lines++ )); # 递增变量的值趋近外层循环
  10. # 使用子 shell 会有问题.
  11. }
  12. done
  13. echo "Number of lines read = $Lines" # 0
  14. # 报错!
  15. echo "------------------------"
  16. exec 3<> myfile.txt
  17. while read line <&3
  18. do {
  19. echo "$line"
  20. (( Lines++ )); # 递增变量的值趋近外层循环.
  21. # 没有子 shell,就不会有问题.
  22. }
  23. done
  24. exec 3>&-
  25. echo "Number of lines read = $Lines" # 8
  26. echo
  27. exit 0
  28. # 下面的行并不在脚本里.
  29. $ cat myfile.txt
  30. Line 1.
  31. Line 2.
  32. Line 3.
  33. Line 4.
  34. Line 5.
  35. Line 6.
  36. Line 7.
  37. Line 8.