子Shell

Bash支持子Shell,子Shell可以执行任意的命令,并且可以返回一个值。一个子shell是由一个shell(或shell脚本)触发的子进程 Bash创建一个子Shell可以分为直接创建和间接创建两种方式,间接方式有3种:命令替换、进程替换和管道。 直接方式有:(...)(...) &command &bash "script content"

  1. (...):创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。
  2. (...) &:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。但是,子Shell运行在后台,不会阻塞当前Shell。
  3. command &:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。但是,子Shell运行在后台,不会阻塞当前Shell。
  4. bash "script content":创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。
  5. command1 | command2:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。
  6. command1 && command2:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。
  7. command1 || command2:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。
  8. command1 ; command2:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。
  9. command1 ; command2 &:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。但是,子Shell运行在后台,不会阻塞当前Shell。

来看一个示例:

#!/bin/bash

(
	echo "I am in a subshell"
)
echo $?
echo "I am in the main shell"

运行结果:

I am in a subshell
0
I am in the main shell

子Shell和变量

子Shell是一个新的进程,它是从父Shell派生出来的,但拥有自己的独立环境。 当启动一个子Shell时,子Shell会继承父Shell的环境,包括环境变量、函数、变量等,子Shell可以更改这些环境,但并不会影响到父SHell。

#!/bin/bash
# subshell.sh

echo

echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
# Bash, 版本3,增加新变量                 $BASH_SUBSHELL 。
echo; echo

outer_variable=Outer
global_variable=
#  定义全局变量来”存储“子shell变量值。

(
echo "We are inside the subshell."
echo "Subshell level INSIDE subshell = $BASH_SUBSHELL"
inner_variable=Inner

echo "From inside subshell, \"inner_variable\" = $inner_variable"
echo "From inside subshell, \"outer\" = $outer_variable"

global_variable="$inner_variable"   #  这会允许”输出“ 一个子shell变量吗?
)

echo; echo
echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
echo

if [ -z "$inner_variable" ]
then
  echo "inner_variable undefined in main body of shell"
else
  echo "inner_variable defined in main body of shell"
fi

echo "From main body of shell, \"inner_variable\" = $inner_variable"
#  $inner_variable 会显示为空白 (未初始化) 
#+ 因为定义在子shell的变量是“局部变量”。
#  有办法改正这一点吗?
echo "global_variable = "$global_variable""  # 为什么这不行?

echo

# =======================================================================

# 另外 ...

echo "-----------------"; echo

var=41                                                 # 全局变量。

( let "var+=1"; echo "\$var INSIDE subshell = $var" )  # 42

echo "\$var OUTSIDE subshell = $var"                   # 41
# 子shell内的变量操作,即使是对全局变量,不影响变量在子shell外的值!


exit 0

#  问题:
#  --------
#  一旦执行一个子shell,
#+ 是否有办法再次进入这个子shell以便修改或调用子shell的变量? 

子Shell常用场景

检查一个变量是否被定义

为什么不再父Shell里面进行检查呢? 在 Bash shell 中,set -u 是一个选项,用于设置 Bash 的 “nounset” 行为。当这个选项被设置时,Bash 会对任何未初始化的变量尝试进行扩展时产生错误。


if (set -u; : $variable) 2> /dev/null
then
  echo "Variable is set."
fi     #  变量已在当前脚本被设定, 
       #+ 或者变量是一个Bash内部变量,
       #+ 或者变量在环境变量中(在export命令后)。

# 也可以写成  [[ ${variable-x} != x || ${variable-y} != y ]]
# 或者       [[ ${variable-x} != x$variable ]]
# 或者       [[ ${variable+x} = x ]]
# 或者       [[ ${variable-x} != x ]]

检查文件锁

if (set -C; : > lock_file) 2> /dev/null
then
  :   # lock_file不存在:没有用户运行此脚本
else
  echo "Another user is already running that script."
exit 65
fi

#  代码段作者 Stéphane Chazelas,
#+ 修改者 Paulo Marcel Coelho Aragao。

并行处理任务

	(cat list1 list2 list3 | sort | uniq > list123) &
	(cat list4 list5 list6 | sort | uniq > list456) &
	# 同时合并和排列两组列表。
	# 在后台运行以确保并行执行。
	#
	# 同样效果如下
	#   cat list1 list2 list3 | sort | uniq > list123 &
	#   cat list4 list5 list6 | sort | uniq > list456 &
	
	wait   # 在子shell结束前不执行之后命令。
	
	diff list123 list456

父子Shell通信

通过中间文件

#!/bin/env bash

(
	var_in_subshell="hello subshell"
	echo "$var_in_subshell" > temp.txt
)
read param < temp.txt
echo "$param"

通过命令替换

#!/bin/env bash

param=$(echo "hello subshell")
echo "$param"

使用命名管道

#!/bin/bash

mkfifo -m 777 npipe
(
  subsend="hello world"
  echo "$subsend" > npipe &
)
read pread < npipe
echo "$pread"
exit 0

使用Here文档

#!/bin/bash

read pvar << HERE
`subvar="hello shell"
echo $subvar`
HERE

echo $pvar