子Shell
Bash支持子Shell,子Shell可以执行任意的命令,并且可以返回一个值。一个子shell是由一个shell(或shell脚本)触发的子进程
Bash创建一个子Shell可以分为直接创建和间接创建两种方式,间接方式有3种:命令替换、进程替换和管道。
直接方式有:(...)
、(...) &
、command &
、 bash "script content"
(...)
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。(...) &
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。但是,子Shell运行在后台,不会阻塞当前Shell。command &
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。但是,子Shell运行在后台,不会阻塞当前Shell。bash "script content"
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。command1 | command2
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。command1 && command2
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。command1 || command2
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。command1 ; command2
:创建一个子Shell,执行完子Shell中的命令后,子Shell退出,返回值是子Shell的退出状态码。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