subshells.html
来自「BASH Shell 编程 经典教程 《高级SHELL脚本编程》中文版」· HTML 代码 · 共 559 行
HTML
559 行
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><HTML><HEAD><TITLE>子shell</TITLE><METANAME="GENERATOR"CONTENT="Modular DocBook HTML Stylesheet Version 1.79"><LINKREL="HOME"TITLE="高级Bash脚本编程指南"HREF="index.html"><LINKREL="UP"TITLE="高级主题"HREF="part4.html"><LINKREL="PREVIOUS"TITLE="通配(globbing)"HREF="globbingref.html"><LINKREL="NEXT"TITLE="受限shell"HREF="restricted-sh.html"></HEAD><BODYCLASS="CHAPTER"BGCOLOR="#FFFFFF"TEXT="#000000"LINK="#0000FF"VLINK="#840084"ALINK="#0000FF"><DIVCLASS="NAVHEADER"><TABLESUMMARY="Header navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><THCOLSPAN="3"ALIGN="center">高级Bash脚本编程指南: 一本深入学习shell脚本艺术的书籍</TH></TR><TR><TDWIDTH="10%"ALIGN="left"VALIGN="bottom"><AHREF="globbingref.html"ACCESSKEY="P">前一页</A></TD><TDWIDTH="80%"ALIGN="center"VALIGN="bottom"></TD><TDWIDTH="10%"ALIGN="right"VALIGN="bottom"><AHREF="restricted-sh.html"ACCESSKEY="N">下一页</A></TD></TR></TABLE><HRALIGN="LEFT"WIDTH="100%"></DIV><DIVCLASS="CHAPTER"><H1><ANAME="SUBSHELLS"></A>20. 子shell</H1><P><ANAME="SUBSHELLSREF"></A></P><P>运行一个shell脚本的时候, 会启动命令解释器的另一个实例. 就好像你的命令是在命令行提示下被解释的一样, 类似于批处理文件中的一系列命令. 每个shell脚本都有效地运行在<AHREF="internal.html#FORKREF">父</A>shell的一个子进程中. 这个<AHREF="internal.html#FORKREF">父</A>shell指的是在一个控制终端或在一个<ICLASS="FIRSTTERM">xterm</I>窗口中给出命令提示符的那个进程. </P><P>shell脚本也能启动它自已的子进程. 这些<EM>子shell</EM>能够使脚本并行的, 有效的, 同时运行多个子任务. </P><TABLECLASS="SIDEBAR"BORDER="1"CELLPADDING="5"><TR><TD><DIVCLASS="SIDEBAR"><P></P><ANAME="AEN14158"></A><P>一般来说, 脚本中的<AHREF="external.html#EXTERNALREF">外部命令</A>能够<AHREF="internal.html#FORKREF">生成(fork)</A>一个子进程, 然而Bash的<AHREF="internal.html#BUILTINREF">内建命令</A>却不会这么做. 也正是由于这个原因, 内建命令比等价的外部命令要执行的快. </P><P></P></DIV></TD></TR></TABLE><P></P><DIVCLASS="VARIABLELIST"><P><B><ANAME="SUBSHELLPARENS1"></A>圆括号中的命令列表</B></P><DL><DT>( command1; command2; command3; ... )</DT><DD><P><TTCLASS="REPLACEABLE"><I>圆括号</I></TT>中命令列表的命令将会运行在一个子shell中. </P></DD></DL></DIV><DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="100%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TDALIGN="LEFT"VALIGN="TOP"><P><ANAME="PARVIS"></A>子shell中的变量对于子shell之外的代码块来说, 是<EM>不</EM>可见的. 当然, <AHREF="internal.html#FORKREF">父进程</A>也不能访问这些变量, 父进程指的是产生这个子shell的shell. 事实上, 这些变量都是<AHREF="localvar.html#LOCALREF">局部变量</A>. </P></TD></TR></TABLE></DIV><DIVCLASS="EXAMPLE"><HR><ANAME="SUBSHELL"></A><P><B>例子 20-1. 子shell中的变量作用域</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING"> 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</PRE></FONT></TD></TR></TABLE><HR></DIV><P>请参考<AHREF="gotchas.html#SUBPIT">例子 31-2</A>. </P><P>+</P><P>子shell中的目录更改不会影响到父shell. </P><DIVCLASS="EXAMPLE"><HR><ANAME="ALLPROFS"></A><P><B>例子 20-2. 列出用户的配置文件</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING"> 1 #!/bin/bash 2 # allprofs.sh: 打印所有用户的配置文件 3 4 # 由Heiner Steven编写, 并由本书作者进行了修改. 5 6 FILE=.bashrc # 在原始脚本中, File containing user profile, 7 #+ 包含用户profile的是文件".profile". 8 9 for home in `awk -F: '{print $6}' /etc/passwd` 10 do 11 [ -d "$home" ] || continue # 如果没有home目录, 跳出本次循环. 12 [ -r "$home" ] || continue # 如果home目录没有读权限, 跳出本次循环. 13 (cd $home; [ -e $FILE ] && less $FILE) 14 done 15 16 # 当脚本结束时,不必使用'cd'命令返回原来的目录. 17 #+ 因为'cd $home'是在子shell中发生的, 不影响父shell. 18 19 exit 0</PRE></FONT></TD></TR></TABLE><HR></DIV><P>子shell可用于为一组命令设置一个<SPANCLASS="QUOTE">"独立的临时环境"</SPAN>. <TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING"> 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不受任何影响, 并且父shell的环境也没有被更改. 15 COMMAND6 16 COMMAND7</PRE></FONT></TD></TR></TABLE> 子shell的另一个应用, 是可以用来检测一个变量是否被定义. <TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING"> 1 if (set -u; : $variable) 2> /dev/null 2 then 3 echo "Variable is set." 4 fi # 变量已经在当前脚本中被设置, 5 #+ 或者是一个Bash的内建变量, 6 #+ 或者是在当前环境下的一个可见变量(指已经被export的环境变量). 7 8 # 也可以写成 [[ ${variable-x} != x || ${variable-y} != y ]] 9 # 或 [[ ${variable-x} != x$variable ]] 10 # 或 [[ ${variable+x} = x ]] 11 # 或 [[ ${variable-x} != x ]]</PRE></FONT></TD></TR></TABLE> 子shell还可以用来检测一个加锁的文件: <TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING"> 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 # 这段程序由Stephane Chazelas所编写, 10 #+ Paulo Marcel Coelho Aragao做了一些修改. </PRE></FONT></TD></TR></TABLE> </P><P>进程在不同的子shell中可以并行地执行. 这样就可以把一个复杂的任务分成几个小的子问题来同时处理. </P><DIVCLASS="EXAMPLE"><HR><ANAME="PARALLEL-PROCESSES"></A><P><B>例子 20-3. 在子shell中进行并行处理</B></P><TABLEBORDER="0"BGCOLOR="#E0E0E0"WIDTH="100%"><TR><TD><FONTCOLOR="#000000"><PRECLASS="PROGRAMLISTING"> 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</PRE></FONT></TD></TR></TABLE><HR></DIV><P>使用<SPANCLASS="QUOTE">"|"</SPAN>管道操作符, 将I/O流重定向到一个子shell中, 比如<KBDCLASS="USERINPUT">ls -al | (command)</KBD>. </P><DIVCLASS="NOTE"><P></P><TABLECLASS="NOTE"WIDTH="100%"BORDER="0"><TR><TDWIDTH="25"ALIGN="CENTER"VALIGN="TOP"><IMGSRC="./images/note.gif"HSPACE="5"ALT="Note"></TD><TDALIGN="LEFT"VALIGN="TOP"><P>在<TTCLASS="REPLACEABLE"><I>大括号</I></TT>中的命令<EM>不会</EM>启动子shell. </P><P>{ command1; command2; command3; . . . commandN; }</P></TD></TR></TABLE></DIV></DIV><DIVCLASS="NAVFOOTER"><HRALIGN="LEFT"WIDTH="100%"><TABLESUMMARY="Footer navigation table"WIDTH="100%"BORDER="0"CELLPADDING="0"CELLSPACING="0"><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top"><AHREF="globbingref.html"ACCESSKEY="P">前一页</A></TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="index.html"ACCESSKEY="H">首页</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top"><AHREF="restricted-sh.html"ACCESSKEY="N">下一页</A></TD></TR><TR><TDWIDTH="33%"ALIGN="left"VALIGN="top">通配(globbing)</TD><TDWIDTH="34%"ALIGN="center"VALIGN="top"><AHREF="part4.html"ACCESSKEY="U">上一级</A></TD><TDWIDTH="33%"ALIGN="right"VALIGN="top">受限shell</TD></TR></TABLE></DIV></BODY></HTML>
⌨️ 快捷键说明
复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?