bash&shell系列文章:http://www.cnblogs.com/f-ck-need-u/p/7048359.html


子shell的概念贯穿整个shell,写shell脚本时更是不可不知。所谓子shell,即从当前shell环境新开一个shell环境,这个新开的shell环境就称为子shell(subshell),而开启子shell的环境称为该子shell的父shell。子shell和父shell的关系其实就是子进程和父进程的关系,只不过子shell和父shell所关联的进程是bash进程。

子shell会从父shell中继承很多环境,如变量、命令全路径、文件描述符、当前工作目录、陷阱等等,但子shell有很多种类型,不同类型的子shell继承的环境不相同。可以使用$BASH_SUBSHELL变量来查看从当前进程开始的子shell层数,$BASHPID查看当前所处BASH的PID,这不同于特殊变量"$$"值,因为"$$"在大多数情况下都会从父shell中继承。

何时产生子shell

要解释清楚子shell以及产生何种类型的子shell,需要搞清楚Linux中如何产生子进程。Linux上创建子进程的方式有三种:一种是fork出来的进程,一种是exec出来的进程,一种是clone出来的进程。此处无需关心clone,因为它用来实现Linux中的线程。

(1).fork是复制进程,它会复制当前进程的副本(不考虑写时复制的模式),以适当的方式将这些资源交给子进程。所以子进程掌握的资源和父进程是一样的,包括内存中的内容,所以也包括环境变量和变量。但父子进程是完全独立的,它们是一个程序的两个实例。

(2).exec是加载另一个应用程序,替代当前运行的进程,也就是说在不创建新进程的情况下加载一个新程序。exec还有一个动作:在进程执行完毕后,退出exec所在的shell环境。

所以为了保证进程安全,若要形成新的且独立的子进程,都会先fork一份当前进程,然后在fork出来的子进程上调用exec来加载新程序替代该子进程。例如在bash下执行cp命令,会先fork出一个bash,然后再exec加载cp程序覆盖子bash进程变成cp进程。

再来说明子shell的问题。一般fork出来的子进程,内容和父进程是一样的(包括变量),例如执行cp命令时也能获取到父进程的变量。但是cp命令在哪里执行呢?执行cp命令敲入回车后,当前的bash进程fork出一个子bash,然后子bash通过exec加载cp程序替代子bash。这算是进入了子shell吗?更通用的问题是:什么情况下会进入子shell环境,什么时候不进入子shel环境呢?

判断是否进入了子shell的方式非常简单,执行"echo $BASHPID",如果该值和父bash进程的pid值不同,则表示进入了子shell。在shell中是否进入子shell的情况可以分为几种:

①.执行bash内置命令时。

bash内置命令是非常特殊的,父进程不会创建子进程来执行这些命令,而是直接在当前bash环境中执行。但如果将内置命令放在管道后,则此内置命令将和管道左边的进程同属于一个进程组,所以仍然会创建子shell。

[root@xuexi ~]# echo $BASHPID   # 当前BASHPID

[root@xuexi ~]# let a=$BASHPID   # bash内置命令,不进入子shell
[root@xuexi ~]# echo $a
[root@xuexi ~]# echo $BASHPID

[root@xuexi ~]# cd | expr $BASHPID      # 管道使得任何命令都进入进程组,会进入子shell

这时候的子shell的作用是为bash内置命令提供执行环境。

②.执行bash命令本身时。

显然它会进入子shell环境,它的绝大多数环境都是新配置的,因为会加载一些环境配置文件。事实上fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量。不妨试试在/etc/bashrc文件中定义一个变量,再在父shell中export名称相同值却不同的环境变量,然后到子shell中看看该变量的值为何?

[root@xuexi ~]# echo "var=55" >>/etc/bashrc
[root@xuexi ~]# export var=
[root@xuexi ~]# bash
[root@xuexi ~]# echo $var

由结果55可知,执行bash时加载的/etc/bashrc中的变量覆盖了父bash中的导出的环境变量值66。

其实执行bash命令,既可以认为进入了子shell,也可以认为没有进入子shell。在执行bash命令后从变量$BASH_SUBSHELL的值为0可以认为它没有进入子shell。但从执行bash命令后进入了新的shell环境来看,它有其父bash进程,且$BASHPID值和父shell不同,所以它算是进入了子shell。

[root@xuexi ~]# echo $BASHPID

[root@xuexi ~]# bash
[root@xuexi ~]# echo $BASHPID

其实,执行bash命令更应该被认为是进入了一个完全独立的、全新的shell环境,而不应该认为是进入了片面的子shell环境。

此外,执行bash命令,"$$"不会继承父shell的值。

③.执行shell脚本时。

脚本中第一行总是"#!/bin/bash"或者直接"bash xyz.sh",这和上面的执行bash进入子shell其实是一回事,都是使用bash命令进入子shell。只不过此时的bash命令和情况②中直接执行bash命令所隐含的选项不一样,所以继承和加载的shell环境也不一样。事实也确实如此,它仅只继承父shell的某些环境变量,其余环境一概初始化。

另外,执行shell脚本相比于直接执行bash命令,还多了一个动作:脚本执行完毕后自动退出子shell。

[root@xuexi ~]# cat b.sh
#!/bin/bash
echo $BASHPID [root@xuexi ~]# echo $BASHPID [root@xuexi ~]# ./b.sh

此外,shell脚本中的"$$"不继承父shell的值。

④.执行shell函数时。

其实shell函数就是命令,它和bash内置命令的情况一样。直接执行时不会进入子shell,但放在管道后会进入子shell。

[root@xuexi ~]# fun_test (){ echo $BASHPID; }   # 定义一个函数,输出BASHPID变量的值
[root@xuexi ~]# echo $BASHPID [root@xuexi ~]# fun_test # 说明执行函数不会进入子shell [root@xuexi ~]# cd | fun_test # 但放在管道后会进入子shell

⑤.执行非bash内置命令时。

例如执行cp命令、grep命令等,它们直接fork一份bash进程,然后使用exec加载程序替代该子bash。此类子进程会继承所有父bash的环境。但严格地说,这已经不是子shell,因为exec加载的程序已经把子bash进程替换掉了,这意味着丢失了很多bash环境。在bash文档中,直接称呼这种环境为"单独的环境",和子shell的概念类似。

[root@xuexi ~]# let a=$BASHPID   # let是内置命令
[root@xuexi ~]# echo $a [root@xuexi ~]# echo $BASHPID # echo是非内置命令,结果是不进入子shell

⑥.命令替换。

当命令行中包含了命令替换部分时,将开启一个子shell先执行这部分内容,再将执行结果返回给当前命令。因为这次的子shell不是通过bash命令进入的子shell,所以它会继承父shell的所有变量内容。这也就解释了"echo $(echo $$)"中"$$"的结果是当前bash的pid号,而不是子shell的pid号,但"echo $(echo $BASHPID)"却和父bash进程的pid不同,因为它不是使用bash命令进入的子shell。

[root@xuexi ~]# echo $BASHPID

[root@xuexi ~]# echo $(echo $BASHPID)      # 使用命令替换$()进入子shell

⑦.使用括号()组合一系列命令。

例如(ls;date;echo haha),独立的括号将会开启一个子shell来执行括号内的命令。这种情况等同于情况⑤。

[root@xuexi ~]# echo $BASHPID

[root@xuexi ~]# (echo $BASHPID)  # 使用括号()的命令组合进入子shell

⑧.放入后台运行的任务

它不仅是一个独立的子进程,还是在子shell环境中运行的。例如"echo hahha &"。

[root@xuexi ~]# echo $BASHPID

[root@xuexi ~]# echo $BASHPID &   # 放入后台运行的任务进入子shell
[]
[root@xuexi ~]# []+ Done echo $BASHPID

⑨.进程替换

既然是新进程了,当然进入子shell执行。例如"cat <(echo haha)"。

[root@xuexi ~]# echo $BASHPID

[root@xuexi ~]# cat <(echo $BASHPID)    # 进程替换"<()"进入子shell

再说明"$$"的继承问题。除了直接执行bash命令和shell脚本这两种子shell,其他进入子shell的情况都会继承父shell的值。前面也已经说了,其实shell脚本和直接执行bash命令开启子shell的方式是一样的,它们都不会继承"$$"值,可以根据上述实验自行测试。

需要说明的是,子shell的环境设置不会粘滞到父shell环境,无论是使用export还是source,它们都只能的概念都是父shell到子shell,不是也不会是从子shell到父shell。也就是说子shell的变量等不会影响父shell。

最后,建议同时阅读另一篇文章:bash启动时环境配置流程,此文中详细解释了bash启动时加载哪些配置文件。

知道了子shell的概念,想必对shell解释器和shell就理解的差不多。于是,下面这张经典的图中为什么出现shell层也很容易理解。说白了,SHELL就是提供了执行环境和解析了命令,并使解析后的命令运行起来。

子shell以及什么时候进入子shell的更多相关文章

  1. Shell 脚本中调用另一个 Shell 脚本的三种方式

    主要以下有几种方式: Command Explanation fork 新开一个子 Shell 执行,子 Shell 可以从父 Shell 继承环境变量,但是子 Shell 中的环境变量不会带回给父 ...

  2. 【Linux】Shell三类变量的作用域——linux shell “永久环境变量”、“临时环境变量”和"普通变量"之完全解读

      2015-05-08 00:15 3896人阅读 评论(10) 收藏 举报 本文章已收录于:   分类: 软件开发进阶(419) 作者同类文章X Unix/Linux杂项(118) 作者同类文章X ...

  3. 013-在 Shell 脚本中调用另一个 Shell 脚本的三种方式

    如下: fork: 如果脚本有执行权限的话,path/to/foo.sh.如果没有,sh path/to/foo.sh. exec: exec path/to/foo.sh source: sourc ...

  4. Shell中要如何调用别的shell脚本,或别的脚本中的变量,函数

    在Shell中要如何调用别的shell脚本,或别的脚本中的变量,函数呢? 方法一:   . ./subscript.sh 方法二:   source ./subscript.sh 注意: 1.两个点之 ...

  5. linux之shell基本认知操作和简单shell练习

    shell编程: 1.Shell的作用 命令解释器,“翻译官”.介于操作系统内核与用户之间,负责解释命令行. shell功能非常强大,除负责解释名另外,还可以将多个命令组合起来,完成复杂的任务,这就是 ...

  6. ipython, 一个 python 的交互式 shell,比默认的python shell 好用得多,支持变量自动补全,自动缩进,支持 bash shell 命令,内置了许多很有用的功能和函数

    一个 python 的交互式 shell,比默认的python shell 好用得多,支持变量自动补全,自动缩进,支持 bash shell 命令,内置了许多很有用的功能和函数. 若用的是fish s ...

  7. MongoDB - The mongo Shell, Write Scripts for the mongo Shell

    You can write scripts for the mongo shell in JavaScript that manipulate data in MongoDB or perform a ...

  8. Linux Shell编程(27)——子shell

    运行一个shell脚本时会启动另一个命令解释器. 就好像你的命令是在命令行提示下被解释的一样, 类似于批处理文件里的一系列命令.每个shell脚本有效地运行在父shell(parent shell)的 ...

  9. Linux Shell 之 我的第一个Shell程序

      这里我首先会介绍一个Shell是什么,再介绍我的第一个Shell程序和从中总结的经验. 一.Shell是什么 在说我的这个Shell程序之前,还是先跟大家说说什么是Shell吧,相信Shell这个 ...

随机推荐

  1. lua的table转为excel表格的方法

    项目中需要用到转表工具,由于没有直接的转表工具,而且嵌套的table(table里面嵌套了多层表格与数组).无奈之下,只好采用折衷的方法,先将table表格转为json数据,再用在线转表工具将json ...

  2. xpath和lxml类库

    1. xpath和lxml lxml是一款高性能的 Python HTML/XML 解析器,我们可以利用XPath,来快速的定位特定元素以及获取节点信息 2. 什么是xpath XPath (XML ...

  3. SQL SERVER占用CPU过高优化

    操作系统是Windows2008R2 ,数据库是SQL2014 64位. 近阶段服务器出现过几次死机,管理员反馈机器内存使用率100%导致机器卡死.于是做了个监测服务器的软件实时记录CPU数据,几日观 ...

  4. 6.装配Bean基于注解

    1.注解:就是一个类,使用@注解名称 开发中:使用注解 取代 xml配置文件. @Component取代<bean class=""> @Component(" ...

  5. WCF双工通信单工通信

    1.单工模式 单向通信,指通信只有一个方向进行,即从客户端流向服务,服务不会发送响应,而客户端也不会期望会有响应.这种情况下,客户端发送消息,然后继续执行 运行后报错: 2.双工模式 双工模式的特点是 ...

  6. 可在广域网部署运行的即时通讯系统 -- GGTalk总览(附源码下载)

      (最新版本:V6.2,2019.01.03 .Xamarin移动端版本已经推出,包括 Android 和 iOS) GGTalk开源即时通讯系统(简称GG)是QQ的高仿版,同时支持局域网和广域网, ...

  7. Maven 项目中的 pom.xml 文件内容说明

    下面是一个比较全面的 POM 文件的结构,当然常用的并不需要这么多配置,视自己的项目需求而定. <project xmlns="http://maven.apache.org/POM/ ...

  8. 走你!Github 开源整合

    加入知识星球,最好的分享交流平台哦~ <我的知识星球,最好的分享交流平台>,一年的服务平台,对于一个成年人来说,就是小费了(更多详情,请点击文章了解)~ 走你!Github 开源整合 1. ...

  9. 如何使用react-redux——傻瓜版

    概述 之前看redux官方文档真是看得一脸懵逼,现在自认为会用了,于是来总结一下用法,供以后开发时参考,相信对其他人也有用. 不得不说,用了redux之后感觉挺爽的,有如下优点: 组件大多是函数组件非 ...

  10. [CocoaPods]pod安装与pod更新

    简介 许多以CocoaPods开头的人似乎认为pod install只在第一次使用CocoaPods设置项目时使用,pod update之后才会使用.但事实并非如此. 本指南的目的是解释何时使用pod ...