Bash 中的環境變數

紫雲飛發表於2015-09-14

在 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 OLDPWD
declare -x PWD="/Users/admin"
declare -x SHLVL="1"

declare -x 1 顯然是非法的,甚至還可以這樣:

$ env -i $'foo\nrm -rf /=' bash -c export

declare -x OLDPWD
declare -x PWD="/Users/admin"
declare -x SHLVL="1"
declare -x foo
rm -rf /

看起來很有風險的樣子,不過目前 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
SHLVL=1
1=1
_=/usr/bin/env

Bash 在啟動其他程式的時候,會把自己所有的變數中帶有 att_exported 屬性且值不是空的變數以及它們的值分別用 = 連線起來合成一個字串陣列,重新復值給 environ 變數,也就是做了和啟動時匯入環境變數相反的操作。

那些沒有等號或者等號左邊沒有字元的環境變數,因為 Bash 在啟動的時候就丟棄了,所以也就沒法傳遞給它的子程式了。

相關文章