Shell子程序
文章目录
1 神秘空间
一些玄幻小说中有这样的设定:主角拥有一方外人不知的“神秘空间”。
在这空间内,他就是主宰,可以收纳各种宝物。但也存在着限制:它默认无法收纳活物,除非是与主角心意相通或毫无敌意的人,方可被引入其中。
这个设定与我们今天要探讨的 Shell子程序 惊人地相似。实际上,我们可以把每一个 Shell程序(进程),都看作一个独立的“空间”。
2 变量 name 的奇妙之旅
《鸟哥的Linux私房菜》中提到一条规则:
若变量需要在其他子程序执行,则需要以
export来使变量变成环境变量,如:export VariableName。
这里的“子程序”究竟是什么?为了理解它,最好的方式莫过于亲手实践。让我们通过一个简单的变量 name 来揭开它的✨神秘面纱✨。
第1步:初探当前空间
我们直接输出变量 name,由于尚未定义,输出为空。这好比我们当前生活的主空间世界里空空如也。
|
|
第2步:在主空间放入“私有宝物”
我们设置变量 name 的值为 test。
|
|
现在,我们的主空间里有了一个名为 name 的“私有宝物”。
第3步:进入一个新的“神秘空间”
我们输入 bash 命令。这个操作等同于在当前的 Shell 空间内,打开了一个全新的、独立的子空间(子程序)。
|
|
你会发现终端看似没有变化,但我们已经身处一个新的环境中了。
第4步:在新空间中寻找宝物
我们再次输出变量 name,却发现值为空。
|
|
这正是“神秘空间”的第一条规则:子空间默认无法访问父空间的“私有宝物”。
刚才定义的变量 name 是父空间的私有财产,子空间对此一无所知。这就出现了为什么在输入 bash 后再输出 name 什么内容也没有看到的现象。
第5步:回归主空间
输入 exit,我们便从子空间退出,回到了父空间(退出 “子程序”回到父程序)。
|
|
再次查看 name,它的值 test 依然存在。
第6步:将“私有宝物”变为“公共信物”
我们使用 export 命令。这个命令的作用,就是将一个“私有宝物”标记为“公共信物”(环境变量),所有从当前空间衍生的新空间都能看到它。
|
|
第7步:带着信物,再探新空间
我们再次输入 bash 进入一个新的子空间(再次进入“子程序”中)。
|
|
第8步:奇迹发生
在新空间中输出变量 name,这次成功地得到了 test!
|
|
这便是 export 的魔力:它打破了空间的壁垒,让变量得以被继承。
第9步:旅程结束
最后,我们再次 exit 退出子空间。
|
|
完整过程如下:

一般情况下,像上面的例子展示的那样,父程序中的自定义变量是无法在子程序中使用的,但是可以通过 export 将 变量转成环境变量 后,就能够在子程序中使用了。
3 到底什么是子程序?
现在我们可以给出清晰的定义了:
在当前的 Shell 中去启用另一个新的 Shell,新的 Shell 就是子程序。
当我们登录 Linux 系统后,会获得一个默认的 Shell(例如 Bash),它拥有一个唯一的进程ID(PID),它是一个独立程序,它会有一个独有的程序识别码,也就是 PID,这就是我们的“主空间”或“父程序”。之后我们执行的许多命令,都会衍生出新的进程,这些就是“子程序”。
为了便于理解画了一个图:

如上图所示,我们在原有的 bash(父程序)中执行另一个 bash 命令时 ,操作环境就切换到了第二个 bash(子程序)。此时,原来的 bash(父程序)就会暂停“挂起(suspended),(就像睡着了,就是 sleep),等待子程序执行完毕。只有当子程序通过 exit 结束时,我们才会回到父程序的环境中。
整个指令运行的环境是实线的部分!若要回到原本的 bash 去,就只有将第二个 bash 结束掉 (下发 exit 或 logout)才行。
这个主从空间的关系遵循两条铁律:
- 子程序只会继承父程序(父空间)的“公共信物”(环境变量),不会继承父程序的“私有宝物”(自定义变量)!在原本 bash 的自定义变量在进入了子程序后就会消失不见,直到离开子程序并回到原本的父程序后,这个变量才会又出现。
- 子空间里发生的任何变化,都不会影响到父空间,即当子程序执行完后,在子程序内定义的各项变量或动作将会结束而不会传回到父程序中。 当子空间关闭时,它内部定义的所有变量和状态都会随之消失。
为解决“子程序无法使用父程序的自定义变量”这一问题,得借助 export 。
export 的作用就是分享自己的变量设置给后来调用的文件或其他程序,如果仅下达 export 而没有接变量,此时就会将所有的环境变量展示出来。
4 一个常见的实践陷阱
让我们看一个在脚本中常遇到的问题。假设有一个 setup.sh 脚本,内容如下:
|
|
我们给它执行权限并运行它,然后尝试读取其中的变量:
|
|
变量明明已经 export 了,为什么还是读不到?
完成过程如下:

原因很简单: 执行 ./setup.sh 这个动作,本身就是创建了一个短暂的“神秘空间”(子进程)。脚本在这个新空间里将所有变量设为“公共信物”,但脚本执行完毕后,这个新空间瞬间就关闭了,里面的所有设置也随之烟消云散,自然不会影响到我们所在的父空间。
这是因为“脚本执行”和“手动输出变量”两个操作是不在同一个 Shell 进程中进行的。我在脚本中定义了变量并使用 export 导出变量,然后在另一个 Shell 窗口或会话中输出变量,此时是无法获取到脚本执行的进程中定义的变量的。因为每个 Shell 窗口或会话都是独立的进程,它们有自己的环境变量。
我们可以通过进程 PID 来验证这一点。$$ 变量可以显示当前 Shell 的 PID,即验证是脚本执行和输出变量两个是不同的 Shell 进程:
$本身就是个变量,表示的是当前这个 Shell 的线程号,亦即是所谓的 PID (Process ID)”
可以在 setup.sh 文件最后一行增加用于输出当前 Shell 进程的 PID 的命令:
|
|
PID 不同,证明它们确实是两个独立的“空间”。
5 解决方案:source
如果想要能够输出脚本内定义的变量,如何实现呢?
在解决上面提出的问题前,这里先介绍 Shell 脚本的两种主流的执行方式:
1、直接执行
- 路径方式执行(不论是绝对路径还是相对路径)
- 利用 bash (或者 sh) 方式执行
这两种直接执行方式执行脚本都会使用一个新的 bash 环境来执行脚本内的指令(新的 shell 进程)。
这种方式会创建一个子 Shell 来执行脚本。如我们所见,这是一种隔离的执行方式,子 Shell 中的变量不会回传给父 Shell。这就像派人去密室修炼,他学到的东西不会为你所用。
也就是说使用这两种执行方式时,其实 script 是在子程序的 bash 内执行的!这里就隐藏了一个概念:“当子程序完成后,在子程序内的各项变量或动作将会结束而不会传回到父程序中”!
例:有脚本 showname.sh 内容如下:
|
|
完整执行过程如下:
|
|

上面例子证明了子程序中的变量在结束后时不会回传到父程序的。
为便于理解结合上面的线路图来解释:
当使用直接执行的方式来处理时,系统会给予一支新的 bash 来让我们来执行 showname.sh 里面的指令,因此两个 firstname, lastname 其实是在上图中子程序 bash 内执行的。
当 showname.sh 执行完毕后,子程序 bash 内的所有数据便被移除,回到父程序,在父程序中输出两个变量就看不到内容了。
2、通过 source 执行
还是上面的 showname.sh 脚本,完整执行过程如下:
|
|
成功了!因为 source 使得所有操作都在“主空间”内发生,变量自然也就留在了当前环境中。这好比你亲自在自己的房间里阅读秘籍并修炼,功力自然地增长在了自己身上。

通过 source 方式执行会让 showname.sh 在父程序中执行,因此各项动作都会在原本的 bash 内生效,这也是有时候在不退出系统时往 ~/.bashrc 写入数据后设置生效时需要执行下 source ~/.bashrc。
这也就是为什么当我们修改了 ~/.bashrc 配置文件后,需要执行 source ~/.bashrc 来让配置立即生效的原因。
source 方式执行流程如下图:

6 总结
- 每个 Shell 都是一个独立的“空间”,默认情况下,变量(私有宝物)无法共享。
export命令可以将变量标记为“环境变量”(公共信物),使其可以被子空间继承。- 执行脚本时,
./script.sh会创建一个临时的子空间来运行,不会影响当前环境。 source script.sh则是在当前空间内执行脚本,会直接修改当前环境,即如果想要能够输出脚本内定义的变量,可以通过 source 方式来执行脚本。
理解了这层“神秘空间”的关系,你就能更深刻地掌握 Shell 变量与进程的奥秘了。