UNIX再學習 -- 環境變數

聚優致成發表於2017-03-15

之前講gcc編譯的時候,參看:C語言再學習 -- GCC編譯過程 提到過靜態庫和共享庫,那時只是簡單的講了下它們相關的編譯連結,接下來就該詳細介紹它們了。不過再講解之前還需瞭解一下程式設計相關的環境變數。

一、環境變數

環境變數一般是指在作業系統中用來指定作業系統執行環境的一些引數,如:臨時資料夾位置和系統資料夾位置等。
環境變數時在作業系統中一個具有特定名字的物件,它包含了一個或者多個應用程式所將使用的資訊。

1、Windows下的環境變數

(1)環境變數配置
右擊我的電腦->屬性->高階系統設定->高階->環境變數->系統變數->選中Path 點選編輯->在Path變數值的最後增加分號(;),再增加新的路徑。
注意:(千萬不要改動之前的變數值)->一路點選確定即可

(2)常見環境變數:(瞭解)
%ALLUSERSPROFILE% 區域性 返回所有“使用者配置檔案”的位置。
%APPDATA% 區域性 返回預設情況應用程式儲存資料的位置。
%CD% 區域性 返回當前目錄字串。
%CMDCMDLINE% 區域性 返回用來啟動當前的 Cmd.exe 的準確命令行。
%CMDEXTVERSION% 系統 返回當前的“命令處理程式擴充套件”的版本號。
%COMPUTERNAME% 系統 返回計算機的名稱。
%COMSPEC% 系統 返回命令列直譯器可執行程式的準確路徑。
%DATE% 系統 返回當前日期。使用與 date /t 命令相同的格式。由 Cmd.exe 生成。有關 date 命令的詳細資訊,請參閱 Date。
%ERRORLEVEL% 系統 返回使用過的命令錯誤程式碼。通常用非零值表示錯誤。
%HOMEDRIVE% 系統 返回連線到使用者主目錄的本地工作站驅動器號。基於主目錄值的設定。使用者主目錄是在“本地使用者和組”中指定的。
%HOMEPATH% 系統 返回使用者主目錄的完整路徑。基於主目錄值的設定。使用者主目錄是在“本地使用者和組”中指定的。
%HOMESHARE% 系統 返回使用者的共享主目錄的網路路徑。基於主目錄值的設定。使用者主目錄是在“本地使用者和組”中指定的。
%LOGONSEVER% 區域性 返回驗證當前登入會話的域控制器的名稱。
%NUMBER_OF_PROCESSORS% 系統 指定安裝在計算機上的處理器的數目。
%OS% 系統 返回作業系統的名稱。Windows 2000 將作業系統顯示為 Windows_NT。
%PATH% 系統 指定可執行檔案的搜尋路徑。
%PATHEXT% 系統 返回作業系統認為可執行的副檔名的列表。
%PROCESSOR_ARCHITECTURE% 系統 返回處理器的晶片體系結構。值: x86,IA64。
%PROCESSOR_IDENTIFIER% 系統 返回處理器說明。
%PROCESSOR_LEVEL% 系統 返回計算機上安裝的處理器的型號。
%PROCESSOR_REVISION% 系統 返回處理器修訂號的系統變數
%PROMPT% 區域性 返回當前解釋程式命令提示符設定。由 Cmd.exe 生成。
%RANDOM% 系統 返回 0 到 32767 之間的任意十進位制數字。由 Cmd.exe 生成。
%SYSTEMDRIVE% 系統 返回包含 Windows XP 根目錄(即系統根目錄)的驅動器
%SYSTEMROOT% 系統 返回 Windows XP 根目錄的位置。
%TEMP% and %TMP% 系統和使用者 返回對當前登入使用者可用的應用程式所使用的預設臨時目錄。有些應用程式需要 TEMP,而其它應用程式則需要 TMP。
%TIME% 系統 返回當前時間。使用與 time /t 命令相同的格式。由 Cmd.exe 生成。有關 time 命令的詳細資訊,請參閱 Time。
%USERDOMAIN% 區域性 返回包含使用者帳戶的域的名稱。
%USERNAME% 區域性 返回當前登入的使用者的名稱。
%UserProfile% 區域性 返回當前使用者的配置檔案的位置。
%WINDIR% 系統 返回作業系統目錄的位置。

2、Linux下的環境變數(重點)

linux是一個多使用者的作業系統,每個使用者登入系統之後,都會有一個專用的執行環境。通常每個使用者預設的環境都是相同的。這個預設的環境實際上就是一組環境變數的定義,使用者可以對自己的執行環境進行定製,其方法就是修改相應的系統環境變數。
在linux中,環境變數一般用大寫加下劃線命名(例如,PATH、ORACLE_HOME)。環境變數就相當於一個指標,當我們要檢視指標所指向的值的時候需要解引用。同樣的,當我們想要檢視環境變數裡面的值的時候,需要在前面加 $ 引用。
linux的變數分為環境變數和本地變數:
環境變數:是一種全域性變數,存在所有的shell中,在登入的時候就有系統定義的環境變數了。linux的環境變數具有繼承性,即shell會繼承父shell的環境變數
本地變數:當前shell中的變數,本地變數中包含環境變數。Linux的本地變數的非環境變數不具備繼承性。
在Linux下面的變數按照生存週期可分為兩類:
永久的:需要修改配置檔案,變數永久生效。
臨時的:使用export命令宣告即可,變數在關閉shell時失效。
修改和檢視環境變數的指令
(1)echo:檢視單個環境變數
例如,顯示環境變數HOME
# echo $HOME
/root
(2)env:檢視所有環境變數
例如,檢視所有的環境變數,可以看一下都有哪些環境變數。
# env
LC_PAPER=en_US.UTF-8
LC_ADDRESS=en_US.UTF-8
SSH_AGENT_PID=1768
LC_MONETARY=en_US.UTF-8
GPG_AGENT_INFO=/tmp/keyring-rB9csr/gpg:0:1
TERM=xterm
SHELL=/bin/bash
XDG_SESSION_COOKIE=619793d1806621208703bfca00000005-1489544306.787815-2133755939
WINDOWID=31457285
LC_NUMERIC=en_US.UTF-8
GNOME_KEYRING_CONTROL=/tmp/keyring-rB9csr
USER=root
.....
可以和grep一起使用檢視某一類的環境變數
# env | grep SH
SSH_AGENT_PID=1768
SHELL=/bin/bash
SSH_AUTH_SOCK=/tmp/keyring-rB9csr/ssh
SHLVL=1
(3)set:檢視本地定義的環境變數
root@ubuntu:~# set
BASH=/bin/bash
BASHOPTS=checkwinsize:cmdhist:expand_aliases:extquote:force_fignore:histappend:hostcomplete:interactive_comments:progcomp:promptvars:sourcepath
BASH_ALIASES=()
BASH_ARGC=()
BASH_ARGV=()
BASH_CMDS=()
BASH_LINENO=()
BASH_SOURCE=()
BASH_VERSINFO=([0]="4" [1]="2" [2]="24" [3]="1" [4]="release" [5]="i686-pc-linux-gnu")
BASH_VERSION='4.2.24(1)-release'
.....
(4)export:設定一個新的環境變數 (臨時的,重啟後消失)
注意,一般環境變數都用大寫加下劃線來命名
例如,新建環境變數HELLO
# export HELLO="hello"
# echo $HELLO
hello
(5)unset:清除環境變數
例如,清除環境變數HELLO,清除後再檢視為空
# unset HELLO
# echo $HELLO

#
(6)readonly:設定只讀環境變數
例如,將環境變數HELLO設為只讀,再修改或者清除該環境變數時會提示為只讀變數,不能再對它進行操作。但是因為它是一個臨時性的環境變數,所以使用者退出登入以後就會自動失效。
# export HELLO="hello"
# readonly HELLO="hello"
# export HELLO="hello" world
bash: HELLO: 只讀變數
# unset HELLO
bash: unset: HELLO: 無法反設定: 只讀 variable
Linux系統常用的環境變數
(1)PATH:決定了shell將到哪些目錄中尋找命令或程式
# echo $PATH
/usr/lib/lightdm/lightdm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/tarena/workdir/toolchains/opt/S5PV210-crosstools/4.4.6/bin
我們可以看到在當前目錄下,預設的PATH的值。它表示當我們在當前目錄下執行一條命令時命令的搜尋路徑。每一個目錄都是以冒號隔開。例如:export PATH=$PATH:/opt/arm-2009q1-203/bin:
當我們執行一個可執行的程式時,系統就會到這些目錄下面去找,在這些目錄下找到的話才執行,否則不執行。
(2)HOME:指定使用者的主目錄(即使用者登入到Linux系統時的預設目錄)
環境變數是一個變數,會隨著使用者的不同,它的值也就不同。
普通使用者下的主目錄:
# echo $HOME
/home/admin
root超級使用者下的主目錄:
# echo $HOME
/root
(3)HISTSIZE:儲存歷史命令記錄的條數
在linux中可以查詢以前輸入的命令,HISTSIZE這個環境的值就表示最多儲存的記錄的數目。
# echo $HISTSIZE
1000
可以看到,上面顯示能夠儲存1000條。
(4)LOGNAME:顯示當前使用者的登入名   (同 指令logname)
echo $LOGNAME
root
(5)HOSTNAME:顯示主機的名字 (同 指令hostname)
echo $HOSTNAME
ubuntu
(6)SHELL:指當前使用者使用的shell型別
echo $SHELL
/bin/bash
(7)LANG/LANGUGE:語言相關的環境變數,多語言可以修改此環境變數
echo $LANG
zh_CN.UTF-8
(8)MAIL:當前使用者的郵件存放的目錄 (無郵件)
# echo $MAIL
(9)PS1:命令基本提示符,對root是 #,對普通使用者是 $
# echo $PS1
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$
(10)PS2:附屬提示符,預設是 >
# echo $PS2
>
存放環境變數的檔案
(1)/etc/profile (所有使用者)
這個檔案是每個使用者登入時都會執行的環境變數設定,當使用者第一次登陸時該檔案被執行,並從/etc/profile.d目錄的配置檔案中搜尋shell的設定。這個檔案的作用就是當使用者登入的時候用於獲取系統的環境變數,只在登入的時候獲取一次。
所以說,在/etc/profile檔案中新增的變數,對所有使用者永久的生效
之前用到過,參看:Hi3516A開發--安裝交叉編譯器
gedit /etc/profile
新增 export PATH="/opt/hisi-linux/x86-arm/arm-hisiv300-linux/target/bin:$PATH"
執行 source /etc/profile
需要注意的是,修改完檔案後想要馬上生效還要執行 source /etc/profile 不然只能在下次重進此使用者時生效。
再如:
#gcc找到標頭檔案的路徑
$C_INCLUDE_PATH=/opt/example/include  
$export C_INCLUDE_PATH  

#g++找到標頭檔案的路徑  
$CPLUS_INCLUDE_PATH=/opt/example/include  
$export CPLUS_INCLUDE_PATH  

#找到靜態庫的路徑  
$LIBRARY_PATH=/opt/example/lib  
$export LIBRARY_PATH 

#找到動態連結庫的路徑
LD_LIBRARY_PATH=/opt/example/lib  
export LD_LIBRARY_PATH 
(2)~/.bash_profile (單個使用者)
每個使用者都可以使用該檔案輸入自己專用的shell資訊,當使用者登入時,該檔案僅僅執行一次。預設情況下,它設定一些環境變數,執行使用者的.bashrc檔案。單個使用者對此檔案的修改只會影響到它以後的每一次登入。
也就是說,在使用者目錄下的.bash_profile檔案中增加變數,僅對當前使用者永久生效,操作同 /etc/profile
(3)/etc/bashrc (所有使用者)
在執行完 /etc/profile 內容後,如果使用者執行 bash shell 的話,則就執行這個檔案,當每次一個新的bash shell 被開啟時,該檔案被讀取所以,如果每開啟一個bash都執行某些操作,就可以在這個檔案裡面設定。
(4)~/.bashrc  (單個使用者)
該檔案只包含專用於你的bash資訊,當你登入時以及每次開啟新的shell時,該檔案就會自動被讀取
gedit .bashrc
新增 export PATH=$PATH:/opt/arm-2009q1-203/bin:
執行 source .bashrc
(5)~/.bash_logout
每次在退出shell的時候會執行該檔案,它提供了定製使用者環境的功能,比如刪除賬號內的臨時檔案等命令就可以放在bash_logout 檔案內,如果這個檔案不存在的話則就執行其他的命令。

3、程式設計相關的環境變數

(1)CPATH/C_INCLUDE_PATH 
C標頭檔案的附加搜尋路徑,相當於gcc的 -I 選項
(2)CPLUS_INCLUDE_PATH
C++標頭檔案的附加搜尋路徑
(3)LIBRARY_PATH
連結時查詢靜態庫和共享庫的路徑,相當於 -I 選項
(4)LD_LIBRARY_PATH
執行時查詢共享庫的路徑

關於Linux gcc中的 LIBRARY_PATH 和 LD_LIBRARY_PATH 引數說明
下面摘取了兩篇較權威的說明資料:
1、 GNU 上關於LIBRARY_PATH的說明:
LIBRARY_PATH
The value of LIBRARY_PATH is a colon-separated list of directories, much like PATH.
When configured as a native compiler, GCC tries the directories thus specified when searching for special linker files, if it can't find them using GCC_EXEC_PREFIX.
Linking using GCC also uses these directories when searching for ordinary libraries for the -l option (but directories specified with -L come first). 
2、 man7 上關於LD_LIBRARY_PATH的說明:
LD_LIBRARY_PATH
A colon-separated list of directories in which to search for
ELF libraries at execution-time.  Similar to the PATH
environment variable.  Ignored in set-user-ID and set-group-ID
programs.
後面發現 StackOverflow 上關於 LIBRARY_PATH 和 LD_LIBRARY_PATH 的解釋更直白:
LIBRARY_PATH is used by gcc before compilation to search for directories containing libraries that need to be linked to your program.

LD_LIBRARY_PATH is used by your program to search for directories containing the libraries after it has been successfully compiled and linked.

EDIT: As pointed below, your libraries can be static or shared.
If it is static then the code is copied over into your program and you don't need to search for the library after your program is compiled and linked.
If your library is shared then it needs to be dynamically linked to your program and that's when LD_LIBRARY_PATH comes into play.

Linux gcc編譯連結時的共享庫搜尋路徑
GCC編譯、連結生成可執行檔案時
,共享庫的搜尋路徑順序如下(注意不會遞迴性地在其子目錄下搜尋):
1、gcc編譯、連結命令中的-L選項;
2、gcc的環境變數的LIBRARY_PATH(多個路徑用冒號分割);
3、gcc預設共享庫目錄:/lib:/usr/lib:usr/lib64:/usr/local/lib。

執行二進位制檔案時的共享庫搜尋路徑
連結生成二進位制可執行檔案後,在執行程式載入共享庫檔案時,搜尋的路徑順序如下:
1、編譯目的碼時指定的共享庫搜尋路徑:用選項 -Wl,rpath 和 include 指定的共享庫的搜尋路徑,比如 gcc -Wl,-rpath,include -L. -ldltest hello.c,在執行檔案時會搜尋路徑 `./include`;
2、環境變數LD_LIBRARY_PATH(多個路徑用冒號分割);
3、在 /etc/ld.so.conf.d/ 目錄下的配置檔案指定的共享庫絕對路徑(通過ldconfig生效,一般是非root使用者時使用);
4、gcc預設共享庫目錄:/lib:/usr/lib:usr/lib64:/usr/local/lib等。

其中,Linux GCC預設的共享庫搜尋路徑可以通過 ld --verbose 命令檢視:
SEARCH_DIR("/usr/i686-linux-gnu/lib32"); SEARCH_DIR("=/usr/local/lib32"); SEARCH_DIR("=/lib32"); SEARCH_DIR("=/usr/lib32"); SEARCH_DIR("=/usr/local/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/local/lib"); SEARCH_DIR("=/lib/i386-linux-gnu"); SEARCH_DIR("=/lib"); SEARCH_DIR("=/usr/lib/i386-linux-gnu"); SEARCH_DIR("=/usr/lib");
總結下來就是,共享庫編譯的時候與靜態庫一樣依賴 LIBRARAY_PATH,執行的旪候依賴 LD_LIBRARY_PATH 。
這也就是我們為什麼講了這麼長時間的環境變數。

4、環境列表

環境列表就是指環境變數的集合,而每個程式都擁有一張獨立的環境列表,來儲存和當前程式相關的環境變數資訊。
環境列表,採用字元指標陣列來儲存所有的環境變數。
例如:
PATH                         0X01  //執行程式所有路徑
CPATH            0X02  //C語言路徑
LIBRARY_PATH    0X03  //靜態庫
LD_LIBRARY_PATH   0X04  //共享庫
char* arr[5] = {0x01, 0x02, 0x03, 0x04};
環境列表採用字元指標陣列來儲存所有的環境變數,使用全域性變數char** environ來記錄環境表的首地址使用NULL 來代表環境表的結束所以訪問環境表則需要藉助environ變數。
//示例一
#include <stdio.h>

int main(void)
{
	//宣告全域性變數
	extern char** environ;
	//給環境表首地址指定替身
	char** p = environ;
	while(*p != NULL)
	{
		printf("%s\n",*p);
		//指向下一個
		p++;
	}
	return 0;
}

功能等同指令env,可自行嘗試
//示例二
#include <stdio.h>
#include <string.h>

int main(void)
{
	//宣告全域性變數
	extern char** environ;
	//給環境表首地址指定替身
	char** p = environ;
	//練習:查詢名字為SHELL的環境變數,獲取SHELL的值存到buf的字元陣列中,然後進行列印
	char buf[20] = {0};
	p = environ;
	while(*p != NULL)
	{
		//比較前6個字元是否相等
		if(!strncmp(*p,"SHELL=",6))
		{
			//跳過環境變數名=
			strcpy(buf,*p+6);
			break;
		}
		//比較下一個
		p++;
	}
	printf("SHELL=%s\n",buf);
	return 0;
}
輸出結果:
SHELL=/bin/bash

功能等同指令echo $SHELL,可自行嘗試

5、環境變數函式

(1)getenv函式
#include <stdlib.h>
char *getenv (const char *name);
函式功能:
表示根據引數指定的環境變數名去環境表進行查詢
返回該環境變數所對應的環境值,查詢失敗則返回NULL.
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
	char *pc = getenv ("SHELL");
	if (NULL == pc)
		perror ("getenv"), exit (-1);
	printf ("SHELL = %s\n", pc);
	return 0;
}
輸出結果:
SHELL = /bin/bash

使用echo指令
# echo $SHELL
/bin/bash
(2)setenv函式
#include <stdlib.h>
int setenv (const char *name, const char *value, int overwrite);
第一個引數:環境變數名
第二個引數:環境變數值
第三個引數:是否修改
函式功能:
改變或增加環境變數,如果引數指定的環境變數不存在則增加。如果存在並且 overwrite 是非0,則修改環境名變數值,否則環境變數不變
成功返回 0,失敗返回 -1
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
	char *pc = getenv ("SHELL");
	if (NULL == pc)
		perror ("getenv"), exit (-1);
	printf ("SHELL = %s\n", pc);

	setenv ("SHELL", "ABC", 1);
	printf ("修改後的SHELL = %s\n", getenv ("SHELL"));
	return 0;
}
輸出結果:
SHELL = /bin/bash
修改後的SHELL = ABC
(3)putenv函式
#include <stdlib.h>
int putenv (char *string);
函式功能:
表示按照引數的內容增加/修改環境變數,其中string的格式為:name=value,如果不存在則新增,存在則刪除
成功返回0,失敗返回-1
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
	char *pc = getenv ("SHELL");
	if (NULL == pc)
		perror ("getenv"), exit (-1);
	printf ("SHELL = %s\n", pc);

		
	int res = putenv ("SHELL=abc");
	if (res)
		perror("putenv"),exit(-1);
	printf ("修改後的SHELL = %s\n", getenv ("SHELL"));

	putenv("CSDN=666");
	printf("增加的環境值是:%s\n",getenv("CSDN"));//123
	return 0;
}
輸出結果:
SHELL = /bin/bash
修改後的SHELL = abc
增加的環境值是:666
(4)unsetenv函式
#include <stdlib.h>
int unsetenv (const char *name);
函式功能:
表示根據引數指定的環境變數去環境表中進行刪除
成功返回0,失敗返回-1
#include <stdio.h>
#include <stdlib.h>

int main (void)
{
	putenv("CSDN=666");
	printf("增加的環境值是:%s\n",getenv("CSDN"));//123

	unsetenv("CSDN");
	printf("刪除的結果是:%s\n",getenv("CSDN"));
	return 0;
}
輸出結果:
增加的環境值是:666
刪除的結果是:(null)
(5)clearenv函式
#include <stdlib.h>
int clearenv (void);
函式功能:
表示清空整個環境表
成功返回0,失敗返回-1

#include <stdio.h>
#include <stdlib.h>

int main (void)
{
	int res;
	putenv("CSDN=666");
	printf("增加的環境值是:%s\n",getenv("CSDN"));//123

	res = clearenv ();
	if(res)
		perror("clearenv"), exit(-1);
	printf("清空整個環境表結束\n");
	printf("CSDN = %s\n",getenv("CSDN"));
	return 0;
}
輸出結果:
增加的環境值是:666
清空整個環境表結束
CSDN = (null)

6、主函式的原型

我們之前將函式的時候提到過,參看:C語言再學習 -- 函式

main函式原型:

int main (int argc, char * argv[], char * envp[]) {....}

第一個引數:命令列引數的個數

第二個引數:命令列引數的地址資訊

第三個引數:環境表的首地址

引數argc表示輸入引數的個數(含命令名),如果有命令列引數,argc應不小於1;argv表示傳入的引數的字串,是一個字串陣列,argv[0]表示命令名,argv[1]指向第一個命令列引數;至於第三個引數env,它與全域性變數environ相比也沒有帶來更多益處,所以POSIX.1也規定應使用environ而不使用第三個引數。通常用getenv和putenv函式來存取特定的環境變數,而不是直接使用environ變數。例如:

//main函式原型的使用  
#include <stdio.h>  
int main(int argc,char* argv[],char* envp[])  
{  
    printf("argc=%d\n",argc);  
    int i=0;  
    for(i=0;i<argc;i++)  
    {  
        printf("感謝%s\n",argv[i]);  
    }  
    //宣告全域性變數  
    extern char** environ;  
    printf("environ=%p,envp=%p\n",environ,envp);//直接訪問環境表  
    return 0;  
}  
輸出結果為:  
argc=1  
感謝./a.out  
environ=0xbfab74cc,envp=0xbfab74cc  


相關文章