寫C/C++程序時,是否注意過main函數的參數?int main(int argc, char *argv[]),以前我們可能覺得這兩個參數可有可無?但我們有沒有想:過程序能不能能接收到外部配置信息?當命令行輸入的指令、程序運行依賴的庫路徑(包括鏈接找動態庫),又是如何被進程識別的?
這些,都涉及一個知識:環境變量。
一.介紹
1.概念
Linux環境變量是系統或用户定義的「k,v結構 鍵值對」,用於為進程提供運行配置(如命令路徑、編碼格式),進程啓動時由內核自動加載,可被所有子進程繼承。
簡單來説,環境變量就是操作系統中用來指定操作系統運行環境的一些參數,對於不用用户有不同的環境變量,系統級環境變量當中通常具有全局特性;
作用:環境變量的參數一定有一些特殊用圖,比如記錄家目錄、用户是誰、路徑 等等。
2.常見的環境變量
幾個與環境變量的操作
echo $NAME// 顯示環境變量信息,NAME是環境變量名;$//用於引入環境變量名PATH=/root//用PATH舉例: PATH指變量名,/root指路徑,覆蓋寫入環境變量PATH=$PATH:/root//追加寫入env:查看系統所有環境變量
常見環境變量:
- PATH:指定命令的搜索路徑(在我們使用一個命令時,操作系統會先遍歷PATH路徑判斷命令是否存在)
在執行ll時,系統回到PATH路徑下查找ll命令,發現存在->執行;而a.out不存在,操作系統不知道它在哪裏,需要我們指明當前路徑"./':
如果我們想讓a.out像系統命令那樣執行,可以把a.out的路徑添加到PATH路徑下:
不用擔心覆蓋以後找不到系統命令,因為這個覆蓋是內存級別的,當我們重新用XShell登錄,所更改環境變量都會恢復原樣。
最後一個問題,為什麼echo可移執行?
PATH是讓Shell查找“外部命令(或者説可執行程序)”,而echo/cd/pwd等是內置命令
- HOME:指定用户的主工作目錄(儲存當前登錄永華的個人主目錄),也就是
cd ~進入的目錄。 - SHELL: 當前Shell,目前使用的命令解釋器程序,它的值通常是/bin/bash
- PWD: 儲存當前用户所在工作目錄路徑,它是實時動態更新的,隨着
cd自動變化;我們用./執行a.out時,相當於告訴系統在PWD變量的當前工作目錄下找a.out這個程序。
既然不同用户下環境變量不同,同時C語言提供了調用接口getenv(),我們能不能實現一份代碼(或者一種指令)在不同用户下得到不同的結果?
二.命令行參數
1.介紹
概念:程序運行時通過命令行傳入的字符串參數,用於靈活控制程序的行為。
也就是説當我們將我們的代碼編譯成可執行程序時,運行程序的命令./a.out將作為參數被main函數接收(argv),同時我們可以對我們的命令待增加選項:
//./mycmd
#include<iostream>
using namespace std;
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
int main(int argc, char *argv[])
{
if(argc != 2)//如果參數數量不等於2
{
//這是我們想通過不同選項達到的不同效果
//這直接用c++的寫了,cout輸出省事一點
cout<<"Usage: "<< argv[0] << "-a/b/c"<<endl;
}
else if(strcmp(argv[1],"-a")==0)
{
cout<<"function1"<<endl;
}
else if(strcmp(argv[1],"-b") == 0)
{
cout<<"function2"<<endl;
}
else if(strcmp(argv[1],"-c")== 0 )
{
cout<<"function3"<<endl;
}
return 0;
}
2.原理
就像上面所説,bash解釋器對命令以空格進行分割,將分割後的字符串傳給argv(儲存在向量表中)。
所以,我們使用strcmp(argv[1],"-a")==0去比較,實現不同選項不同效果。
三.命令行參數與環境變量
env
main函數的第三個參數env可以讓我們得到“啓動時”所有的環境變量:
//./mygetenv
#include<stdio.h>
int main(int argc,char *argv[],char *env[])
{
int i =0;
for(;env[i];i++)
{
printf("env[%d] -> %s\n",i,env[i]);//輸出環境變量
}
return 0;
}
除此之外,可以通過第三方變量直接得到環境變量
environ
//./Environ
#include<stdio.h>
int main()
{
//libc中定義的全局變量量environ指向環境變量表,
//environ沒有包含在任何頭文件中,所以在使用時 要用extern聲明。
extren char **environ;
//通過參數傳遞,得到環境變量
//還可以通過地址空間
int i =0;
for(;environ[i];i++)
{
cout<<i<<" "<<environ[i]<<endl;
}
return 0;
}
Linux下,每個程序都有一張獨立的環境變量表environ,在fork()創建子進程時這張表會被複制,每個進程間環境變量表互不影響;
getenv()
getenv()是讓我們獲取特定的一個環境變量的值
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("%s\n", getenv("PATH"));//得到PATH
return 0;
}
兩張核心向量表
目前為止,我們瞭解到linux中兩張核心的向量表:
- 命令行參數表
- 定義:程序運行時用户手動傳入的參數集合。
- 載體: main(int argc, char* argv[]) , argc 記參數個數(含程序路徑), argv[] 存具體參數。
- 特性:按約定順序傳入,僅當前運行週期有效。
- 環境變量表
- 定義:存儲程序運行所需系統/用户配置的集合。
- 載體:全局變量 char** environ ,元素為“鍵=值”格式字符串。
- 特性:按鍵匹配取值,子進程可繼承,支持長期/臨時生效。
四.環境變量
1.環境變量具有全局屬性
操作系統的"第一份"環境變量由內核初始化;我們所運行的進程,一般情況下都是子進程;bash啓動時,會從操作系統的配置文件中讀取環境變量信息,之後在bash上運行我們自己的子進程會繼承父進程的環境變量;再在子進程上創建子進程也是一樣會獲得同樣的環境變量——所以説,環境變量具有全局屬性。
2.添加環境變量
export: 添加環境變量
export創建的環境變量,會在之後的進程中不斷被繼承下去;
unset:取消環境變量
命令行直接unset MY_VAL就會發現我們的環境變量消失了;不只是我們自己添加的,原本存在的也可以被取消,unset PATH等等
3.本地變量
如果我們直接MY_VAL=123這樣去添加環境變量,會發現echo也能查找到,這與export有什麼區別?
但如果我們env去查看,又發現MY_VAL並不存在,為什麼?
這是因為,用 變量名=值添加出的變量是本地變量。
本地變量:僅在當前Shell會話/腳本代碼塊(如if、for) 內生效的變量,不影響其他Shell進程,關閉會話或退出代碼塊後自動失效。
本地變量是不能被子進程繼承的,對於查看所有變量(包括本地變量),可以使用set;echo $變量名查看特定變量;
五.擴展:內建命令
指令通常被分為兩種:
- 常規命令:通過創建子進程完成
- 內建命令:bash不創建子進程,而是由自己親自執行(類似於bash調用了自己的或者是系統提供的函數)
echo、pwd、export、cd等是常見的內建命令;
它的關鍵意義主要在於兩點:
- 提升效率:避免進程創建/銷燬的資源消耗,執行速度遠快於外部命令(如 cd 、 echo 等高頻命令需快速響應)。
- 操作Shell內部狀態:可直接修改Shell環境(如 echo 可以查看不被子進程繼承的本地變量),外部命令無法操作Shell內部數據。
總結
- 程序運行需要兩張表:①命令行參數②環境變量;這兩張表由bash(系統)維護;
- main也是函數,也可以傳參,可以被bash調用(得到兩張表的內容)
除此之外,若想對環境變量有更通透的理解,一篇文章當然不夠,需要我們主動尋找,在不同場景下實踐積累。