20.1 使用 exec

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

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

1. #!/bin/bash
2. # 使用 'exec' 重定向 标准输入 .

5. exec 6<&0          # 链接文件描述符 #6 到标准输入.
6. # .

8. exec < data-file   # 标准输入被文件 "data-file" 替换

10. read a1            # 读取文件 "data-file" 首行.
11. read a2            # 读取文件 "data-file" 第二行

13. echo
14. echo "Following lines read from file."
15. echo "-------------------------------"
16. echo $a1
17. echo $a2

19. echo; echo; echo

21. exec 0<&6 6<&-
22. #  现在在之前保存的位置将从文件描述符 #6 将 标准输出 恢复.
23. #+ 且关闭文件描述符 #6 ( 6<&- ) 让其他程序正常使用.
24. #
25. # <&6 6<&-    also works.

27. echo -n "Enter data  "
28. read b1  # 现在按预期的,从正常的标准输入 "read".
29. echo "Input read from stdin."
30. echo "----------------------"
31. echo "b1 = $b1"

33. echo

35. exit 0

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

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

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

1. #!/bin/bash
2. # reassign-stdout.sh

4. LOGFILE=logfile.txt

6. exec 6>&1           # 链接文件描述符 #6 到标准输出.
7. # 保存标准输出.

9. exec > $LOGFILE     # 标准输出被文件 "logfile.txt" 替换.

11. # ----------------------------------------------------------- #
12. # 所有在这个块里的命令的输出都会发送到文件 $LOGFILE.

14. echo -n "Logfile: "
15. date
16. echo "-------------------------------------"
17. echo

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

27. # ----------------------------------------------------------- #

29. exec 1>&6 6>&-      # 关闭文件描述符 #6 恢复 标准输出.

31. echo
32. echo "== stdout now restored to default == "
33. echo
34. ls -al
35. echo

37. exit 0

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

1. #!/bin/bash
2. # upperconv.sh
3. # 转化指定的输入文件成大写.

5. E_FILE_ACCESS=70
6. E_WRONG_ARGS=71

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                   #  同样的错误退出
14. #+ 等同如果输入文件 ($1) 未指定 (为什么?).

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

24. exec 4<&0
25. exec < $1            # 将从输入文件读取.

27. exec 7>&1
28. exec > $2            # 将写入输出文件.
29. # 假定输出文件可写 (增加检测?).

31. # -----------------------------------------------
32. cat - | tr a-z A-Z   # 转化大写.
33. #   ^^^^^                # 读取标准输入.
34. #           ^^^^^^^^^^   # 写到标准输出.
35. # 然而标准输入和标准输出都会被重定向.
36. # 注意 'cat' 可能会被遗漏.
37. # -----------------------------------------------

39. exec 1>&7 7>&-       # 恢复标准输出.
40. exec 0<&4 4<&-       # 恢复标准输入.

42. # 恢复后, 下面这行会预期从标准输出打印.
43. echo "File \"$1\" written to \"$2\" as uppercase conversion."

45. exit 0

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

样例 20-4. 规避子 shell

1. #!/bin/bash
2. # avoid-subshell.sh
3. # Matthew Walker 的建议.

5. Lines=0

7. echo

9. cat myfile.txt | while read line;
10. do {
11. echo $line
12. (( Lines++ ));  #  递增变量的值趋近外层循环
13. #  使用子 shell 会有问题.
14. }
15. done

17. echo "Number of lines read = $Lines"     # 0
18. # 报错!

20. echo "------------------------"

23. exec 3<> myfile.txt
24. while read line <&3
25. do {
26. echo "$line"
27. (( Lines++ ));                   #  递增变量的值趋近外层循环.
28. #  没有子 shell,就不会有问题.
29. }
30. done
31. exec 3>&-

33. echo "Number of lines read = $Lines"     # 8

35. echo

37. exit 0

39. # 下面的行并不在脚本里.

41. $ cat myfile.txt

43. Line 1.
44. Line 2.
45. Line 3.
46. Line 4.
47. Line 5.
48. Line 6.
49. Line 7.
50. Line 8.