16.1. 使用exec

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


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

#!/bin/bash
# 使用'exec'重定向stdin. 


exec 6<&0          # 将文件描述符#6与stdin链接起来. 
                   # 保存stdin. 

exec < data-file   # stdin被文件"data-file"所代替. 

read a1            # 读取文件"data-file"的第一行. 
read a2            # 读取文件"data-file"的第二行. 

echo
echo "Following lines read from file."
echo "-------------------------------"
echo $a1
echo $a2

echo; echo; echo

exec 0<&6 6<&-
#  现在将stdin从fd #6中恢复, 因为刚才我们把stdin重定向到#6了, 
#+ 然后关闭fd #6 ( 6<&- ), 好让这个描述符继续被其他进程所使用. 
#
# <&6 6<&-    这么做也可以. 

echo -n "Enter data  "
read b1  # 现在"read"已经恢复正常了, 就是能够正常的从stdin中读取.
echo "Input read from stdin."
echo "----------------------"
echo "b1 = $b1"

echo

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

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

LOGFILE=logfile.txt

exec 6>&1           # 将fd #6与stdout链接起来. 
                    # 保存stdout. 

exec > $LOGFILE     # stdout就被文件"logfile.txt"所代替了. 

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

echo -n "Logfile: "
date
echo "-------------------------------------"
echo

echo "Output of \"ls -al\" command"
echo
ls -al
echo; echo
echo "Output of \"df\" command"
echo
df

# ----------------------------------------------------------- #

exec 1>&6 6>&-      # 恢复stdout, 然后关闭文件描述符#6. 

echo
echo "== stdout now restored to default == "
echo
ls -al
echo

exit 0


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

#!/bin/bash
# upperconv.sh
# 将一个指定的输入文件转换为大写. 

E_FILE_ACCESS=70
E_WRONG_ARGS=71

if [ ! -r "$1" ]     # 判断指定的输入文件是否可读? 
then
  echo "Can't read from input file!"
  echo "Usage: $0 input-file output-file"
  exit $E_FILE_ACCESS
fi                   #  即使输入文件($1)没被指定
                     #+ 也还是会以相同的错误退出(为什么?). 

if [ -z "$2" ]
then
  echo "Need to specify output file."
  echo "Usage: $0 input-file output-file"
  exit $E_WRONG_ARGS
fi


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

exec 7>&1
exec > $2            # 将写到输出文件中. 
                     # 假设输出文件是可写的(添加检查?). 

# -----------------------------------------------
    cat - | tr a-z A-Z   # 转换为大写. 
#   ^^^^^                # 从stdin中读取. 
#           ^^^^^^^^^^   # 写到stdout上. 
# 然而, stdin和stdout都被重定向了. 
# -----------------------------------------------

exec 1>&7 7>&-       # 恢复stout.
exec 0<&4 4<&-       # 恢复stdin.

# 恢复之后, 下边这行代码将会如预期的一样打印到stdout上. 
echo "File \"$1\" written to \"$2\" as uppercase conversion."

exit 0

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


例子 16-4. 避免子shell

#!/bin/bash
# avoid-subshell.sh
# 由Matthew Walker所提出的建议. 

Lines=0

echo

cat myfile.txt | while read line;  #  (译者注: 管道会产生子shell)
                 do {
                   echo $line
                   (( Lines++ ));  #  增加这个变量的值
                                   #+ 但是外部循环却不能访问. 
                                   #  子shell问题. 
                 }
                 done

echo "Number of lines read = $Lines"     # 0
                                         # 错误!

echo "------------------------"


exec 3<> myfile.txt
while read line <&3
do {
  echo "$line"
  (( Lines++ ));                   #  增加这个变量的值
                                   #+ 现在外部循环就可以访问了. 
                                   #  没有子shell, 现在就没问题了. 
}
done
exec 3>&-

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

echo

exit 0

# 下边这些行是这个脚本的结果, 脚本是不会走到这里的. 

$ cat myfile.txt

Line 1.
Line 2.
Line 3.
Line 4.
Line 5.
Line 6.
Line 7.
Line 8.