第20章. 子shell(Subshells)

运行一个shell脚本时会启动另一个命令解释器. 就好像你的命令是在命令行提示下被解释的一样, 类似于批处理文件里的一系列命令.每个shell脚本有效地运行在父shell(parent shell)的一个子进程里.这个父shell是指在一个控制终端或在一个xterm窗口中给你命令指示符的进程.

shell脚本也能启动他自已的子进程. 这些子shell(即子进程)使脚本并行地,有效率地地同时运行多个子任务.

圆括号里的命令列表

( 命令1; 命令2; 命令3; ... )

嵌在圆括号里的一列命令在一个子shell里运行.

在子shell里的变量不能被这段子shell代码块之外外面的脚本访问.这些变量是不能被产生这个子shell的父进程(parent process)存取的,实际上它们是局部变量(local variables).


例子 20-1. 子shell中的变量作用域

   1 #!/bin/bash
   2 # subshell.sh
   3 
   4 echo
   5 
   6 echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
   7 # Bash, 版本 3, 增加了新的              $BASH_SUBSHELL 变量.
   8 echo
   9 
  10 outer_variable=Outer
  11 
  12 (
  13 echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
  14 inner_variable=Inner
  15 
  16 echo "From subshell, \"inner_variable\" = $inner_variable"
  17 echo "From subshell, \"outer\" = $outer_variable"
  18 )
  19 
  20 echo
  21 echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
  22 echo
  23 
  24 if [ -z "$inner_variable" ]
  25 then
  26   echo "inner_variable undefined in main body of shell"
  27 else
  28   echo "inner_variable defined in main body of shell"
  29 fi
  30 
  31 echo "From main body of shell, \"inner_variable\" = $inner_variable"
  32 #  $inner_variable 会以没有初始化的变量来打印
  33 #+ 因为变量是在子shell里定义的"局部变量".
  34 #  这个有办法补救的吗?
  35 
  36 echo
  37 
  38 exit 0

参考例子 31-2.

+

在子shell中的目录更改不会影响到父shell.


例子 20-2. 列出用户的配置文件

   1 #!/bin/bash
   2 # allprofs.sh: 打印所有用户的配置文件
   3 
   4 # 由 Heiner Steven编写, 并由本书作者修改.
   5 
   6 FILE=.bashrc  #  在一般的脚本里,包含用户配置的文件是".profile".
   7               #
   8 
   9 for home in `awk -F: '{print $6}' /etc/passwd`
  10 do
  11   [ -d "$home" ] || continue    # 如果没有家目录,跳过此次循环.
  12   [ -r "$home" ] || continue    # 如果目录没有读权限,跳过此次循环.
  13   (cd $home; [ -e $FILE ] && less $FILE)
  14 done
  15 
  16 #  当脚本终止时,不必用'cd'命令返回原来的目录,
  17 #+ 因为'cd $home'是在子shell中发生的,不影响父shell.
  18 
  19 exit 0

子shell可用于为一组命令设定临时的环境变量.
   1 COMMAND1
   2 COMMAND2
   3 COMMAND3
   4 (
   5   IFS=:
   6   PATH=/bin
   7   unset TERMINFO
   8   set -C
   9   shift 5
  10   COMMAND4
  11   COMMAND5
  12   exit 3 # 只是从子shell退出.
  13 )
  14 # 父shell不受影响,变量值没有更改.
  15 COMMAND6
  16 COMMAND7
它的一个应用是测试是否一个变量被定义了.
   1 if (set -u; : $variable) 2> /dev/null
   2 then
   3   echo "Variable is set."
   4 fi     #  变量已经在当前脚本中被设置,
   5        #+ 或是Bash的一个内部变量,
   6        #+ 或是可见环境变量(指已经被导出的环境变量).
   7 
   8 # 也可以写成            [[ ${variable-x} != x || ${variable-y} != y ]]
   9 # 或                    [[ ${variable-x} != x$variable ]]
  10 # 或                    [[ ${variable+x} = x ]]
  11 # 或                    [[ ${variable-x} != x ]]
另一个应用是检查一个加锁的文件:
   1 if (set -C; : > lock_file) 2> /dev/null
   2 then
   3   :   # lock_file 不存在,还没有用户运行这个脚本
   4 else
   5   echo "Another user is already running that script."
   6 exit 65
   7 fi
   8 
   9 #  由St閜hane Chazelas编程
  10 #+ 由Paulo Marcel Coelho Aragao修改.

进程在不同的子shell中可以串行地执行.这样就允许把一个复杂的任务分成几个小的子问题来同时地处理.


例子 20-3. 在子shell里进行串行处理

   1 	(cat list1 list2 list3 | sort | uniq > list123) &
   2 	(cat list4 list5 list6 | sort | uniq > list456) &
   3 	#列表的合并和排序同时进.
   4 	#放到后台运行可以确保能够串行执行.
   5 	#
   6 	#和下面的有相同的作用:
   7 	#   cat list1 list2 list3 | sort | uniq > list123 &
   8 	#   cat list4 list5 list6 | sort | uniq > list456 &
   9 	
  10 	wait   #在所有的子shell执行完成前不再执行后面的命令.
  11 	
  12 	diff list123 list456

"|"管道操作把I/O流重定向到子shell,例如ls -al | (command).

在一个花括号内的代码块不会运行一个子shell.

{ command1; command2; command3; ... }