Bash 是如何从环境变量中导入函数的
在上文中曾说到:
- 所谓的环境变量的真实面目其实就是个任意字符串
- Bash 在启动时会将 environ 数组中包含 = 号的字符串导入成为自己的变量
- Bash 在启动外部命令时会将自己内部标记为环境变量的变量重组成字符串数组赋值给 environ
本文中继续深入讲三点:
- environ 数组中可能存在 = 左边名字相同的元素,也就是同名的环境变量,Bash 是怎么导入的?
- Bash 还可以从环境变量中导入函数,甚至同时导入两个同名的变量和函数
- Bash 还可以同时导出两个同名的变量和函数
如果有两个同名的环境变量,很简单,那么后面的值会覆盖前面的:
$ env foo=1 foo=2 bash -c 'echo $foo' 2 |
上篇文章中我们没有提到过函数,Bash 其实是可以从环境变量中导入函数的,比如下面这样:
$ foo() { echo foo函数; } $ export -f foo $ bash $ foo foo函数 |
上一级的 Shell 把函数传给了它的 child shell,Bash 是怎么实现的呢?我们用 env 命令演示一下:
$ env 'BASH_FUNC_foo%%=() { echo foo函数; }' bash -c 'foo' foo函数 |
其实 Bash 就是把满足 "BASH_FUNC_函数名%%=(){ 函数体" 格式的环境变量作为函数源码解析并导入。所以两个同名的变量和函数并不会冲突,可以同时导入,像这样:
$ env 'foo=1' 'BASH_FUNC_foo%%=() { echo $1; }' bash -c 'foo $foo' 1 |
既然可以同时导入,那么导出更没问题了:
$ foo=1 $ foo(){ echo foo函数; } $ export foo;export -f foo $ env ... foo=1 BASH_FUNC_foo%%=() { echo foo函数 } ... |
Bash 4.3.30 之前的版本
注意,本文所讲的表现仅适用于 Bash 4.3.30 及之后的版本,之前的 Bash 版本在导出函数时不会给函数名加上 BASH_FUNC_ 前缀和 %% 后缀,在导入时也不会识别前缀后缀,只要看到 = 右边是 "() {" 这四个字符,就按函数导入,像这样:
$ env 'foo=() { echo foo函数; }' bash -c 'foo' foo函数 |
由于环境变量字符串的转换和识别规则不同,假如你在 Bash 4.3.30 中打开一个 Bash 3.2.25,后者是无法继承到前者导出的函数的:
$ bash4.3.30 $ foo() { echo foo函数 ; } $ export -f foo $ bash3.2.25 $ foo bash3.2.25: foo: command not found |
反之亦然,同时 foo 会被导入成一个变量:
$ bash3.2.25 $ foo() { echo foo函数 ; } $ export -f foo $ bash4.3.30 $ foo bash3.2.25: foo: command not found $ echo $foo () { echo foo函数 } |