先前講過,exec
系統調用執行新程序時會把命令行參數和環境變數表傳遞給main
函數,它們在整個進程地址空間中的位置如下圖所示。
和命令行參數argv
類似,環境變數表也是一組字元串,如下圖所示。
libc
中定義的全局變數environ
指向環境變數表,environ
沒有包含在任何標頭檔中,所以在使用時要用extern
聲明。例如:
例 30.1. 打印環境變數
#include <stdio.h> int main(void) { extern char **environ; int i; for(i=0; environ[i]!=NULL; i++) printf("%s\n", environ[i]); return 0; }
執行結果為
$ ./a.out SSH_AGENT_PID=5717 SHELL=/bin/bash DESKTOP_STARTUP_ID= TERM=xterm ...
由於父進程在調用fork
創建子進程時會把自己的環境變數表也複製給子進程,所以a.out
打印的環境變數和Shell進程的環境變數是相同的。
按照慣例,環境變數字元串都是name=value
這樣的形式,大多數name
由大寫字母加下劃線組成,一般把name
的部分叫做環境變數,value
的部分則是環境變數的值。環境變數定義了進程的運行環境,一些比較重要的環境變數的含義如下:
執行檔的搜索路徑。ls
命令也是一個程序,執行它不需要提供完整的路徑名/bin/ls
,然而通常我們執行當前目錄下的程序a.out
卻需要提供完整的路徑名./a.out
,這是因為PATH
環境變數的值裡麵包含了ls
命令所在的目錄/bin
,卻不包含a.out
所在的目錄。PATH
環境變數的值可以包含多個目錄,用:
號隔開。在Shell中用echo
命令可以查看這個環境變數的值:
$ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
當前Shell,它的值通常是/bin/bash
。
當前終端類型,在圖形界面終端下它的值通常是xterm
,終端類型決定了一些程序的輸出顯示方式,比如圖形界面終端可以顯示漢字,而字元終端一般不行。
語言和locale,決定了字元編碼以及時間、貨幣等信息的顯示格式。
當前用戶主目錄的路徑,很多程序需要在主目錄下保存配置檔案,使得每個用戶在運行該程序時都有自己的一套配置。
用environ
指針可以查看所有環境變數字元串,但是不夠方便,如果給出name
要在環境變數表中查找它對應的value
,可以用getenv
函數。
#include <stdlib.h> char *getenv(const char *name);
getenv
的返回值是指向value
的指針,若未找到則為NULL
。
修改環境變數可以用以下函數
#include <stdlib.h> int setenv(const char *name, const char *value, int rewrite); void unsetenv(const char *name);
putenv
和setenv
函數若成功則返回為0,若出錯則返回非0。
setenv
將環境變數name
的值設置為value
。如果已存在環境變數name
,那麼
若rewrite非0,則覆蓋原來的定義;
若rewrite為0,則不覆蓋原來的定義,也不返回錯誤。
unsetenv
刪除name
的定義。即使name
沒有定義也不返回錯誤。
例 30.2. 修改環境變數
#include <stdlib.h> #include <stdio.h> int main(void) { printf("PATH=%s\n", getenv("PATH")); setenv("PATH", "hello", 1); printf("PATH=%s\n", getenv("PATH")); return 0; }
$ ./a.out PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games PATH=hello $ echo $PATH /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games
可以看出,Shell進程的環境變數PATH
傳給了a.out
,然後a.out
修改了PATH
的值,在a.out
中能打印出修改後的值,但在Shell進程中PATH
的值沒變。父進程在創建子進程時會複製一份環境變數給子進程,但此後二者的環境變數互不影響。