Shell

了解不同上下文中的環境變數

  • April 7, 2014

試圖了解 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

通常,變數出現在envset出現在發送到的環境變數列表中execve。一些locale變數出現在envor中,set而另一些則沒有出現(LC_TYPE,LC_COLLATELC_MESSAGE, 以及LC_ALL但具有空值)。最後,其他變數沒有定義,env儘管它們具有可見的效果 ( PS1),如 反映的那樣set

這裡發生了什麼?envset(不帶參數),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傳遞給子程序。

如果您將 afork()放入該範例中,您會看到情況就是這樣,並且(重申),這個分叉和可能執行的過程是子程序是如何創建並**environ從其祖先繼承的。exec呼叫替換過程映像,但根據man execvman environ(注意,前者的某些版本不引用此),**environ由系統傳遞。

/usr/bin/env這是通過MYFOO=whatbar?導出的文字 fork 和 exec putenv()

#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 實例的私有數據。setBash 將通過沒有參數的方式向您展示這個 + 繼承的環境內容。請注意,此輸出還包括源函式。

但是,如果我發現,例如,使用 env | 的 LC_CTYPE grep “LC_CTYPE”,它不發送任何輸出。一般來說,locale 顯示 13 個 LC_* 變數,而 env 只有 9 個:

我從(just ) 中沒有得到任何LC_變數,但從. 我認為這些是通過呼叫設置的變數而不是導出的;您從中獲得任何資訊的事實可能反映了某處某些配置中的幼稚錯誤。env``LANG``locale``locale``env

引用自:https://unix.stackexchange.com/questions/123383