运行 shell 脚本会启动一个新的进程,即一个 子 shell。
定义一个 子 shell 是由 shell(或 shell 脚本)启动的 子进程。 |
子 shell 是命令处理器的一个独立实例 -- 即在控制台或 xterm 窗口中为您提供提示符的 shell。正如您的命令在命令行提示符下被解释一样,脚本也以 批处理 方式处理一系列命令。每个正在运行的 shell 脚本实际上都是 父 shell 的一个子进程(子进程)。
一个 shell 脚本本身可以启动子进程。这些 子 shell 允许脚本进行并行处理,实际上是同时执行多个子任务。
#!/bin/bash
# subshell-test.sh
(
# Inside parentheses, and therefore a subshell . . .
while [ 1 ] # Endless loop.
do
echo "Subshell running . . ."
done
)
# Script will run forever,
#+ or at least until terminated by a Ctl-C.
exit $? # End of script (but will never get here).
Now, run the script:
sh subshell-test.sh
And, while the script is running, from a different xterm:
ps -ef | grep subshell-test.sh
UID PID PPID C STIME TTY TIME CMD
500 2698 2502 0 14:26 pts/4 00:00:00 sh subshell-test.sh
500 2699 2698 21 14:26 pts/4 00:00:24 sh subshell-test.sh
^^^^
Analysis:
PID 2698, the script, launched PID 2699, the subshell.
Note: The "UID ..." line would be filtered out by the "grep" command,
but is shown here for illustrative purposes. |
通常,脚本中的一个 外部命令 会 fork 出一个子进程,[1] 而 Bash 内建命令 则不会。因此,内建命令的执行速度比其等效的外部命令更快,并且使用的系统资源更少。
子 shell 中的变量在子 shell 的代码块外部是不可见的。它们无法被 父进程,即启动子 shell 的 shell 访问。实际上,这些变量是 局部 于 子进程 的变量。
示例 21-1. 子 shell 中的变量作用域
#!/bin/bash
# subshell.sh
echo
echo "We are outside the subshell."
echo "Subshell level OUTSIDE subshell = $BASH_SUBSHELL"
# Bash, version 3, adds the new $BASH_SUBSHELL variable.
echo; echo
outer_variable=Outer
global_variable=
# Define global variable for "storage" of
#+ value of subshell variable.
(
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" # Will this allow "exporting"
#+ a subshell variable?
)
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 will show as blank (uninitialized)
#+ because variables defined in a subshell are "local variables".
# Is there a remedy for this?
echo "global_variable = "$global_variable"" # Why doesn't this work?
echo
# =======================================================================
# Additionally ...
echo "-----------------"; echo
var=41 # Global variable.
( let "var+=1"; echo "\$var INSIDE subshell = $var" ) # 42
echo "\$var OUTSIDE subshell = $var" # 41
# Variable operations inside a subshell, even to a GLOBAL variable
#+ do not affect the value of the variable outside the subshell!
exit 0
# Question:
# --------
# Once having exited a subshell,
#+ is there any way to reenter that very same subshell
#+ to modify or access the subshell variables? |
![]() | 虽然 $BASH_SUBSHELL 内部变量指示子 shell 的嵌套级别,但 $SHLVL 变量在子 shell 中没有变化。
|
在子 shell 中进行的目录更改不会传递到父 shell。
示例 21-2. 列出用户配置文件
#!/bin/bash
# allprofs.sh: Print all user profiles.
# This script written by Heiner Steven, and modified by the document author.
FILE=.bashrc # File containing user profile,
#+ was ".profile" in original script.
for home in `awk -F: '{print $6}' /etc/passwd`
do
[ -d "$home" ] || continue # If no home directory, go to next.
[ -r "$home" ] || continue # If not readable, go to next.
(cd $home; [ -e $FILE ] && less $FILE)
done
# When script terminates, there is no need to 'cd' back to original directory,
#+ because 'cd $home' takes place in a subshell.
exit 0 |
子 shell 可以用于为命令组设置一个 "专用环境"。
COMMAND1 COMMAND2 COMMAND3 ( IFS=: PATH=/bin unset TERMINFO set -C shift 5 COMMAND4 COMMAND5 exit 3 # Only exits the subshell! ) # The parent shell has not been affected, and the environment is preserved. COMMAND6 COMMAND7 |
这种 "专用环境" 的一个应用是测试变量是否已定义。
if (set -u; : $variable) 2> /dev/null
then
echo "Variable is set."
fi # Variable has been set in current script,
#+ or is an an internal Bash variable,
#+ or is present in environment (has been exported).
# Could also be written [[ ${variable-x} != x || ${variable-y} != y ]]
# or [[ ${variable-x} != x$variable ]]
# or [[ ${variable+x} = x ]]
# or [[ ${variable-x} != x ]] |
另一个应用是检查锁文件
if (set -C; : > lock_file) 2> /dev/null then : # lock_file didn't exist: no user running the script else echo "Another user is already running that script." exit 65 fi # Code snippet by St�phane Chazelas, #+ with modifications by Paulo Marcel Coelho Aragao. |
+
进程可以在不同的子 shell 中并行执行。这允许将复杂的任务分解为并发处理的子组件。
示例 21-3. 在子 shell 中运行并行进程
(cat list1 list2 list3 | sort | uniq > list123) & (cat list4 list5 list6 | sort | uniq > list456) & # Merges and sorts both sets of lists simultaneously. # Running in background ensures parallel execution. # # Same effect as # cat list1 list2 list3 | sort | uniq > list123 & # cat list4 list5 list6 | sort | uniq > list456 & wait # Don't execute the next command until subshells finish. diff list123 list456 |
将 I/O 重定向到子 shell 使用 "|" 管道操作符,例如ls -al | (命令).
![]() | 在 花括号 之间的代码块不会启动子 shell。 { 命令 1; 命令 2; 命令 3; . . . 命令 N; }
|
| [1] | 使用 exec 调用的外部命令不会(通常)fork 出子进程/子 shell。 |