了解不同上下文中的環境變數
試圖了解 Linux(具體是 Ubuntu 13.04)中環境的行為,我發現在不同的上下文中使用或定義設置環境變數的不同情況。例如,如果我檢查,
locale
,我會得到:$ locale LANG=en_US.UTF-8 LANGUAGE=es_ES:es_HN:es_EC:en LC_CTYPE="en_US.UTF-8" LC_NUMERIC=es_ES.UTF-8 // more output
但是,如果我發現,例如,
LC_CTYPE
使用env | grep "LC_CTYPE"
,它不會發送任何輸出。一般來說,locale
顯示 13 個LC_*
變數,env
但只有 9 個:$ locale | grep "LC_*" | wc -l 13 $ env | grep "LC_*" | wc -l 9
其他具有不同“性質”的變數是
PS1
。例如:$ env | grep "PS1" # No output, but... $ set | grep "PS1" | head -n 1 PS1=$'\\[\\033[1;33m\\][\\t][\\W]\342\230\233\\[\\033[0m\\] '
當然,
PS1
在我目前的環境中是一個定義明確的變數,因為我看到我的提示相應地改變了。在其他上下文中查看環境變數的其他方法是通過
strace
. 它是一個程序,可讓您在執行程序時查看正在發生的事情。樣本:$ strace -v ./a.out # a.out is a common Hello World, made in C. execve("./a.out", ["./a.out"], ["LC_PAPER=es_ES.UTF-8", ...]) = 0 brk(0) access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) # etc, etc write(1, "Hello World\n", 12Hello World ) = 12 exit_group(0) = ?
shell 在執行程序時做的第一件事是呼叫
execve
,它確實呼叫了程序。它的第一個參數是被呼叫的程序,第二個是被argv
呼叫程序的參數,第三個參數是環境變數。例如,在第 3 個參數中,既不出現,也不
PS1
出現LC_TYPE
。通常,變數出現在
env
或set
出現在發送到的環境變數列表中execve
。一些locale
變數出現在env
or中,set
而另一些則沒有出現(LC_TYPE
,LC_COLLATE
和LC_MESSAGE
, 以及LC_ALL
但具有空值)。最後,其他變數沒有定義,env
儘管它們具有可見的效果 (PS1
),如 反映的那樣set
。這裡發生了什麼?
env
,set
(不帶參數),locale
(顯然僅尊重語言環境變數)之間有什麼區別?
這裡的主要問題——例如為什麼
$PS1
不被報告env
——是env
從非互動式環境報告。程序是從互動式shell的一個分支執行的,但是它們的環境設置方式有一個微妙之處:它實際上是通過為所有exec()
’d 程序設置的本機 C 級外部變數集繼承的(請參閱 參考資料man environ
)。這是一個插圖:#include <stdio.h> extern char **environ; int main (void) { int i; for (i = 0; environ[i] != NULL; i++) { printf("%s\n", environ[i]); } return 0; }
有趣的是,如果您編譯並執行它,您會發現與
**environ
報告的內容完全匹配env
:$ gcc test.c $ ./a.out > aout.txt $ env > env.txt $ diff env.txt aout.txt 68c68 < _=/bin/env --- > _=./a.out
唯一的區別是執行檔的名稱。那麼
**environ
從哪裡來,為什麼不包含,例如,$PS1
?基本解釋是,程序總是作為其他程序的子程序創建的,它們繼承
**environ
,但PS1
從來不是它的一部分。在啟動時,shell 可能會從標準位置獲取變數,這些位置根據 shell 是否是互動式的而有所不同;請參閱中的呼叫man bash
。這方面的一個方面是:PS1 已設置
$$ … $$ 如果 bash 是互動式的,則允許 shell 腳本或啟動文件測試此狀態。
現在,請注意
/etc/bashrc
以下內容:# are we an interactive shell? if [ "$PS1" ]; then
這是您設置實際(花哨)提示的位置,並且它和初始值
$PS1
都沒有被export
編輯過。初始值是由 shell 在呼叫時創建的,因為它是互動式的,然後它獲取那個文件——但PS1
沒有放入**environ
. 如果你執行,你可以看到:#!/bin/sh echo $PS1
什麼都沒有——即使你
echo $PS1
在你的互動式 shell 中定義了它。這是因為**environ
執行#!/bin/sh
的 與父互動 shell 相同,但不包含PS1
. 這意味著每個 shell 都使用一個單獨的全域變數內部表,但最初是從其中填充的**environ
(這令人困惑,因為這意味著**environ
不包括許多稱為*環境變數*的東西)。的內容
**environ
在 中/proc/[PID]/environ
,如果您檢查目前互動式 shell 的內容cat /proc/$BASHPID/environ
,您會看到PS1
不存在。但是東西是如何進入“環境”的呢?
簡單的答案是通過系統呼叫。例如,如果我們在前面的範例 C 程序中加入一些東西:
#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> extern char **environ; int main (void) { int i; if (putenv("MYFOO=whatbar?")) { fprintf(stderr, "putenv() failed: %s\n", strerror(errno)); exit(1); } for (i = 0; environ[i] != NULL; i++) { printf("%s\n", environ[i]); } return 0; }
MYFOO=whatbar?
將在輸出中(請參閱man putenv
)。由於 shell 通過fork()
ing (複製父程序的記憶體堆棧)然後呼叫execv()
(傳遞 duplicated**environ
)來創建程序,我們可以看到一種機制,通過該機制可以將環境變數export
傳遞給子程序。如果您將 a
fork()
放入該範例中,您會看到情況就是這樣,並且(重申),這個分叉和可能執行的過程是子程序是如何創建並**environ
從其祖先繼承的。exec
呼叫替換過程映像,但根據man execv
和man environ
(注意,前者的某些版本不引用此),**environ
由系統傳遞。
/usr/bin/env
這是通過MYFOO=whatbar?
導出的文字 fork 和 execputenv()
:#include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> extern char **environ; int main (void) { pid_t pid; if (putenv("MYFOO=whatbar?")) { fprintf(stderr, "putenv() failed: %s\n", strerror(errno)); exit(1); } pid_t pid = fork(); if (!pid) execl("/usr/bin/env", "env", NULL); return 0; }
那麼,不在“環境”中的東西在哪裡?
它是特定 shell 實例的私有數據。
set
Bash 將通過沒有參數的方式向您展示這個 + 繼承的環境內容。請注意,此輸出還包括源函式。但是,如果我發現,例如,使用 env | 的 LC_CTYPE grep “LC_CTYPE”,它不發送任何輸出。一般來說,locale 顯示 13 個 LC_* 變數,而 env 只有 9 個:
我從(just ) 中沒有得到任何
LC_
變數,但從. 我認為這些是通過呼叫設置的變數而不是導出的;您從中獲得任何資訊的事實可能反映了某處某些配置中的幼稚錯誤。env``LANG``locale``locale``env