Bash 中的环境变量
在 Bash 里,可以通过 export 命令查看当前 Shell 进程的环境变量,这些环境变量一些是 Bash 自己创建的,还有一些是 Bash 从父进程继承来的,然而需要注意的是,父进程传给 Bash 的环境变量不一定是我们想象的那样。
在 C 语言层面,环境变量是存放在一个名为 environ 的全局变量里的,这个变量的值是一个字符串的数组,像这样:
{"foo=1", "bar=2"}
在父子孙一辈辈进程中传递的就是这么个数组,我们嘴里说的环境变量的原貌其实就是个字符串,而不是我们通常在高级语言里看到的键值对。然而操作系统并没有对环境变量字符串的格式做任何限制,environ 变量的值还可以是这样:
{"1=1", "=2", "="}
甚至这样:
{"foo\nbar=1"}
Bash 在启动的时候,会检查 environ 数组中的每个字符串,如果它包含 =,且 = 左边有任意的字符,就把它从 = 分割开,一个做变量名,一个做值,变成自己的变量。咦? "1=1" 也会导入成变量? 是的,在 Bash 的实现当中,是这样的,只是这样的变量会被加上特殊的属性标记。
变量名称合法的变量被添加的属性是:
att_exported | att_imported
一个表示是要导出给子进程的环境变量,一个表示是从环境变量导入的变量。
变量名不合法的变量被添加的属性是:
att_exported | att_imported | att_invisible
多了一个 invisible 属性,表示不可见。
在 Bash 启动之后,各个内部命令可以通过这个标记判断是不是自己想要的变量。比如 set 命令就不会输出 1 这个变量:
$ env -i 1=1 bash -c set | grep '1='
|
但 export 命令就会:
$ env -i 1=1 bash -c export declare -x 1 |
declare -x 1 显然是非法的,甚至还可以这样:
$ env -i $'foo\nrm -rf /=' bash -c export declare -x OLDPWD |
看起来很有风险的样子,不过目前 Bash 4.4 beta 版本,已经修复了这个问题,export 命令不再输出那些变量名不合法的变量了,下面是 export 命令源码做的改动:
+ /* If we imported a variable that's not a valid identifier, don't + show it in any lists. */ + if ((var->attributes & (att_invisible|att_imported)) == (att_invisible|att_imported)) + continue;
那 Bash 在启动其他程序的时候,会把这样的环境变量传递给子进程吗?答案是会的,我们可以通过在两个 env 命令中间插入一个 bash 命令来看出效果:
$ env -i 1=1 bash -c env PWD=/Users/admin |
Bash 在启动其他程序的时候,会把自己所有的变量中带有 att_exported 属性且值不是空的变量以及它们的值分别用 = 连接起来合成一个字符串数组,重新复值给 environ 变量,也就是做了和启动时导入环境变量相反的操作。
那些没有等号或者等号左边没有字符的环境变量,因为 Bash 在启动的时候就丢弃了,所以也就没法传递给它的子进程了。