实验
- 情形1
直接在终端中执行exit:
$ exit 1
这时候,终端会直接退出(logout)。
- 情形2
我们新建一个文件 test.sh,写上以下内容:
exit 1
然后再在终端中执行:
$ bash test.sh # 如果给予 test.sh 执行权限,然后执行 ./test.sh 效果是相同的
这时候,终端不退出,并且能够捕获上一次命令的执行状态:
$ echo $?
结果:1
。
- 情形3
还是执行上述test.sh文件,但是我们不使用bash
命令,而改用source
命令:
source test.sh
结果:终端退出。
分析
情形1和情形2的区别在哪里呢?
为什么情形2终端不退出呢?
-
打开一个虚拟终端,将自动进入bash进程中,这个过程发生了什么?
这应该是:操作系统新建一个“虚拟终端”进程,该进程fork
一个bash进程。于是,我们就进入了bash进程环境。 -
exit
命令究竟是在做什么?
它的作用是终止当前进程,并且将进程终止状态返回给父进程。 -
bash中直接执行
exit 1
,发生了什么?
- 我们首先要思考,bash进程开启之后,为什么没有自动退出,这一定是bash进程执行了一个
while(1)
循环,不然进程执行完所有语句,就退出了。 - 这个
while(1)
循环在等待终端的输入,所以,当我们键入 “exit 1” 字符串,然后按下Enter之后,while(1)
循环解析这个字符串,然后调用exit()
函数退出当前进程,那么问题来了,当前进程是什么?当然是我们的bash进程。 - bash进程退出,为什么终端也会退出呢?这取决于“虚拟终端”程序的逻辑,当然可以设定成:一旦子进程bash退出,父进程也退出。
-
如果我们执行
bash test.sh
命令,发生了什么?
这时候,bash进程会再次fork
一个新的bash进程(暂命名为“bash2”),bash2进程再来解析这个 .sh 文件,当解析到 “exit 1” 字符串时,bash2进程退出。但是父进程bash依然存在,并且捕获到子进程的退出状态1
。 -
情形3验证了我们的第 4 点,因为
source
命令与bash
命令不同,source
会在当前的bash进程中直接解析 .sh 文件,而不是新开一个进程。
环境变量
在 shell 中运行一个新程序(不必是子 shell),是先 fork 一个子进程,然后使用 exec 将原先的二进制代码替换为新进程的程序。所以,shell 的普通变量无法在新进程中起作用。而环境变量则会将 shell 的环境变量复制一份到子进程中。