父shell和子shell

得到当前shell的进程号

执行

echo $BASHPID

或者

echo $$

如果该值和父bash进程的pid值不同,则表示进入了子shell。

开启和关闭子shell

首次进入bash

记录shell tree形状和当前shell的进程号

pstree

在这里插入图片描述

echo $$

在这里插入图片描述

开启一个子bash
bash
记录开启一个子bash之后shell tree形状和当前shell的进程号

在这里插入图片描述

在这里插入图片描述

退出当前bash
exit
记录退出一个子bash之后shell tree形状和当前shell的进程号

在这里插入图片描述

在这里插入图片描述

何时产生子shell

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

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

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

为了保证进程安全,若要形成新的且独立的子进程,都会先fork一份当前进程,然后在fork出来的子进程上调用exec来加载新程序替代该子进程

除了上面可以通过bash命令手动开启一个子shell;例如在bash下执行cp命令,会先fork出一个bash,然后再exec加载cp程序覆盖子bash进程变成cp进程。

不进入子shell的情况:
1.执行bash内置命令时

bash内置命令是非常特殊的,父进程不会创建子进程来执行这些命令,而是直接在当前bash环境中执行

echo $BASHPID 

let a=$BASHPID

echo $a

let是内置命令,不进入子shell

在这里插入图片描述

2.执行shell函数时

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

定义一个函数,输出BASHPID变量的值

fun_test (){ echo $BASHPID; } 

得到当前BASHPID

echo $BASHPID

执行函数

fun_test 

在这里插入图片描述

执行函数前后BASHPID相同说明没有进入子shell

3.执行非bash内置命令时

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

echo $BASHPID

expr $BASHPID

cd是内置命令、expr是外部命令

在这里插入图片描述

进入子shell的情况:
1.将命令放在管道后

如果将命令放在管道后,则此命令将和管道左边的进程同属于一个进程组,所以仍然会创建子shell。这时候的子shell的作用是为bash内置命令提供执行环境。

echo "https://blog.csdn.net/qq_43808700?spm=1000.2115.3001.5343" | read

echo $REPLY

echo是内置命令、read也是内置命令

使用 read 读取数据时,如果没有提供变量名,那么读取到的数据将存放到环境变量REPLY中

使用管道操作符“|”可以把一个命令的标准输出传送到另一个命令的标准输入中,连续的|意味着第一个命令的输出为第二个命令的输入,第二个命令的输出为第三个命令的输入,依次类推…。

read内置命令位于管道后,read会在子shell中执行

echo命令在父Shell中执行,echo通过管道将内容输出到子进程中,管道可以用于父子进程之间通信,因此子shell可以拿到父shell输出的内容,因此子shell中read一定将拿到的内容保存在了环境变量REPLY中,而子进程的环境变量对父进程是不可见的,所以读取失败。

在这里插入图片描述

echo $BASHPID


cd | expr $BASHPID 

在这里插入图片描述

echo $BASHPID


ls | expr $BASHPID 

在这里插入图片描述

cd是内置命令,ls是非内置命令,expr 1也是非内置命令

结论:只要放在管道符之后的命令,则此命令将和管道左边的进程同属于一个进程组 2,就会创建子shell,管道符右边的命令就会在子shell中运行

2.执行bash命令本身时

bash命令本身是bash内置命令,在当前shell环境下执行内置命令本不会创建子shell,也就是说不会有独立的bash进程出现,而实际结果则表现为新的bash是一个子进程。其中一个原因是执行bash命令会加载各种环境配置项,为了父bash的环境得到保护而不被覆盖,所以应该让其以子shell的方式存在。虽然fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量。

切换到root用户

su root

在/etc/bashrc文件中定义一个变量

echo "var=55" >>/etc/bashrc

echo $var //55 只有文件中定义的变量

再在当前shell中export名称相同值却不同的环境变量

export var=66

echo $var //66 此时在当前shell中查看var的值就是66,加载变量的顺序是先环境变量再/etc/bashrc文件中的变量

echo和export都是内置命令因此不会产生新的子shell

然后到子shell中看看该变量的值

bash
echo $var

在这里插入图片描述

发现是55,虽然fork出来的bash子进程内容完全继承父shell,但因重新加载了环境配置项,所以子shell没有继承普通变量,更准确的说是覆盖了从父shell中继承的变量。

在这里插入图片描述

BASH_SUBSHELL 是记录一个 Bash 进程实例中多个子Shell(subshell)嵌套深度的累加器。bash前后BASH_SUBSHELL的值都为0,可以认为没有进入子shell。

bash前后BASHPID的值不同可以认为算是进入了子shell。

结论:其实执行bash命令,既可以认为进入了子shell,也可以认为没有进入子shell。从bash是内置命令的角度来考虑,它不会进入子shell,这一点在执行bash命令后从变量 B A S H S U B S H E L L 的 值 为 0 可 以 验 证 出 来 。 但 从 执 行 b a s h 命 令 后 进 入 了 新 的 s h e l l 环 境 来 看 , 它 有 其 父 b a s h 进 程 , 且 BASH_SUBSHELL的值为0可以验证出来。但从执行bash命令后进入了新的shell环境来看,它有其父bash进程,且 BASHSUBSHELL0bashshellbashBASHPID值和父shell不同,所以它算是进入了子shell。

3.执行shell脚本时

创建一个sh文件

#!/bin/bash
echo $BASHPID
echo $SHLVL

SHLVL是记录多个 Bash 进程实例嵌套深度的累加器

保存为test_bash.sh

获取当前BASHPID

echo $BASHPID

执行test_bash.sh脚本文件

sh -x test_bash.sh 

使用sh -x调试shell脚本,只需要文件所有者拥有可读权限就可以正常调试3。或者切换到root用户修改文件权限,就可以./test_bash.sh执行

在这里插入图片描述

发现一旦执行sh文件获取的BASHPID就发生了改变,说明产生了子shell

(如果想要./test_bash.sh运行,需要切换到root用户,更改文件权限

查看test_bash.sh权限

ls -l | grep test_bash

在这里插入图片描述

对所有者增加可执行权限(对test_bash.sh赋可执行的权限 chmod 731 test_bash.sh or chmod u+x test_bash.sh):

chmod u+x test_bash.sh

在这里插入图片描述

)

4.执行shell函数时

其实shell函数放在管道后会进入子shell。

定义一个函数,输出BASHPID变量的值

fun_test (){ echo $BASHPID; } 

得到当前BASHPID

echo $BASHPID

放在管道后会执行

cd | fun_test

在这里插入图片描述

执行函数前后BASHPID不同说明进入子shell

5.命令替换

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

echo $BASHPID

echo $ $$

echo $(echo $BASHPID) 

在这里插入图片描述

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

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

echo $BASHPID

(echo $BASHPID)

在这里插入图片描述

使用括号()的命令组合进入子shell

7.放入后台运行的任务

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

echo $BASHPID

echo $BASHPID &  

echo $BASHPID & :放入后台运行的任务进入子shell

在这里插入图片描述

8.进程替换

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

echo $BASHPID

cat <(echo $BASHPID) 

cat <(echo $BASHPID) :进程替换"<()"进入子shell

在这里插入图片描述

除了直接执行bash命令和shell脚本这两种子shell,其他进入子shell的情况都会继承父shell的值。前面也已经说了,其实shell脚本和直接执行bash命令开启子shell的方式是一样的,它们都不会继承父shell的值

查看shell树

pstree

上半部分shell树

在这里插入图片描述

往下观察,就能看到我们刚执行的pstree命令开启了一个子shell

在这里插入图片描述

连续exit两次并查看shell tree

在这里插入图片描述

发现少了两个bash,结果与操作相符

在这里插入图片描述

继续exit发现会退出shell,说明已经到父shell,再退出就会退出整个shell

在这里插入图片描述


  1. expr命令是一个手工命令行计数器,用于在UNIX/LINUX下求表达式变量的值,一般用于整数值,也可用于字符串。 ↩︎

  2. ↩︎

  3. ↩︎

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值