目錄
一、基本概念及其核心作用
1、基本概念
2、核心作用
二、常見環境變量
三、查看環境變量方法
四、測試PATH
1、對比執行:./project和直接執行project的區別
2、思考:為何某些命令可直接執行而無需路徑,但我們的二進制程序卻需要指定完整路徑?
3、PATH 解析
各目錄的作用
關鍵點
1. 查找順序
2.?sbin?vs?bin
3. 用戶級目錄
4、方法一:將可執行程序拷貝到環境變量PATH的某一路徑下
5、方式二:將可執行程序所在的目錄導入到環境變量PATH當中
6、關于export PATH=$PATH:/home/hmz的一些小知識
具體解析
注意事項(重要!!!)
永久生效的方法(以?~/.bashrc?為例):
五、測試HOME
1、分別以root和普通用戶執行echo $HOME,對比輸出差異
普通用戶:
超級用戶:
2、執行cd ~; pwd,理解~與$HOME的關系
1.?~?的作用
2.?$HOME?的作用
3.?~?與?$HOME?的關系
4.?示例驗證
腳本內容 (compare_home_tilde.sh)
使用方法
5.?注意事項
總結
六、測試SHELL
七、環境變量相關命令
1、echo:顯示指定環境變量的值
2、export:設置新的環境變量
1?? 設置臨時環境變量
2?? 查看變量值
3?? 驗證是環境變量(對子進程可見)
命令分解解釋
為什么用這個命令測試?export?
4?? 刪除環境變量(下面會介紹)
5?? 驗證已刪除
3、env:顯示所有環境變量
環境變量說明
4、unset:刪除環境變量
1. 添加測試環境變量
2. 驗證變量是否存在
3. 使用?unset?清除變量
4. 再次驗證變量
5. 檢查是否從環境變量中移除
關鍵說明:
5、set:顯示本地定義的Shell變量和環境變量
1.?查看所有變量(快速測試)
2.?開啟/關閉調試模式
📌?總結
八、環境變量的組織方式
九、main函數的參數(超重點!!!)
1、無參數形式
2、帶參數形式
參數解析:
argc(Argument Count)
argv(Argument Vector)
示例代碼:
運行示例:
1. 參數切分的參與者
2. 關鍵細節
3、擴展:envp(環境變量,非標準)
注意事項:
4、編寫命令選項選擇
十、_start函數與main函數的關系
1、_start:程序的真正入口
2、main:開發者定義的入口
3、關鍵區別
4、示例流程
5. 特殊情況
6、總結
十一、通過代碼獲取環境變量
方法一:使用命令行第三個參數
方法二:通過第三方變量environ獲取
十二、通過系統調用獲取或設置環境變量
getenv?和?putenv?函數解析
1.?getenv?函數(獲取環境變量)
函數原型
功能
示例
輸出示例
注意事項
2.?putenv?函數(設置/修改環境變量)
函數原型
功能
示例
輸出示例
注意事項
3. 主要區別
4. 綜合示例
輸出示例
5. 安全注意事項
6. 替代方案(POSIX)
示例
輸出
7、總結
十三、環境變量通常具備全局屬性
1、核心知識點
環境變量的全局特性
(1) 初次運行程序無輸出
(2) 導出環境變量
(3) 再次運行程序輸出結果
2、思考現象背后的原理:為什么會出現這種現象?
進程環境變量繼承機制
為什么需要?export?
十四、環境變量和本地變量
1、作用范圍
2、定義與導出方式
環境變量
本地變量
3、子進程繼承性
4、常見用途
5、查看與刪除
6、擴展知識
7、總結
十五、關于內置命令的必知知識
1、內置命令的特點
2、常見內置命令分類
1. 基礎操作
2. 變量管理
3. 流程控制
4. 作業控制
3、如何判斷命令是否為內置?
4、與外部命令的區別
5、注意事項
一、基本概念及其核心作用
1、基本概念
????????環境變量(Environment Variables)是操作系統中用于動態配置程序運行環境的一種機制。它們以鍵值對(Key-Value)的形式存儲,可以被操作系統、應用程序或腳本讀取和修改。
舉例:
????????在C/C++編程時,雖然我們不知道動態/靜態庫的具體位置,但鏈接仍能成功,正是因為有環境變量幫助編譯器定位這些庫文件
-
定義:環境變量(environment variables)是操作系統或用戶定義的用于指定運行環境參數的全局變量,用于存儲系統路徑、配置參數、臨時數據等。
-
作用范圍:
-
系統級:影響所有用戶和進程(如?
PATH
)。 -
用戶級:僅對當前用戶生效(如?
HOME
)。 -
進程級:由父進程傳遞給子進程(如通過腳本設置的變量)。
-
2、核心作用
-
配置管理:避免硬編碼路徑或參數(如數據庫連接字符串)。
-
跨進程通信:父進程通過環境變量向子進程傳遞信息。
-
系統行為控制:例如?
NODE_ENV=production
?決定 Node.js 運行模式。 -
簡化命令:將常用目錄加入?
PATH
?后可直接執行程序(如?python
、git
)。
二、常見環境變量
變量名 | 作用 |
---|---|
PATH | 定義可執行文件的搜索路徑,多個路徑用分隔符(: 或; )分隔。 |
HOME | 當前用戶的主目錄路徑(如 Linux 的?/home/user )。 |
USER /USERNAME | 當前登錄的用戶名。 |
TEMP /TMP | 臨時文件目錄路徑(如 Windows 的?C:\Temp )。 |
LANG | 系統語言和編碼(如?en_US.UTF-8 )。 |
JAVA_HOME | 指向 Java 安裝路徑,供開發工具(如 Maven)使用。 |
SHELL | 當前Shell,它的值通常是/bin/bash。 |
三、查看環境變量方法
我們可以通過echo命令來查看環境變量,方式如下:
echo?$NAME?//NAME為待查看的環境變量名稱
例如,查看環境變量PATH:
echo $PATH
四、測試PATH
1、對比執行:./project
和直接執行project
的區別
如下,你是否想過,為什么直接輸入ls
就能運行,而運行自己編譯的程序卻必須加上./
前綴?
2、思考:為何某些命令可直接執行而無需路徑,但我們的二進制程序卻需要指定完整路徑?
????????系統默認能直接執行某些命令(如ls)而無需添加路徑,是因為這些命令的存儲位置已被記錄在環境變量PATH中。當用戶輸入命令時,系統會自動搜索PATH中指定的目錄來查找對應程序。
????????但對于用戶自己創建的可執行程序,由于不在PATH包含的目錄中,必須通過添加./前綴明確指明程序位于當前目錄。我們可以通過查看PATH環境變量來確認系統默認的搜索路徑:
????????環境變量PATH包含多個由冒號分隔的路徑。執行ls命令時,Shell會按照從左到右的順序依次在這些路徑中進行查找。
3、PATH 解析
各目錄的作用
目錄 | 用途 | 適用場景 |
---|---|---|
/usr/local/bin | 手動安裝的第三方軟件 | make install 、手動編譯的程序 |
/usr/bin | 系統核心命令和包管理器安裝的軟件 | ls 、grep 、python3 (系統自帶) |
/usr/local/sbin | 手動安裝的系統管理命令 | 如手動編譯的?nginx 、redis-server |
/usr/sbin | 系統自帶的管理員命令 | iptables 、useradd (通常需要?sudo ) |
~/.local/bin | 用戶級 Python/Rust/Node.js 工具 | pip install --user 、cargo install |
~/bin | 用戶自定義腳本和程序 | 個人使用的 Shell 腳本、手動放置的可執行文件 |
關鍵點
1. 查找順序
-
Shell 會從左到右查找命令,第一個匹配到的可執行文件會被執行。
示例:-
如果?
/usr/local/bin/python3
?和?/usr/bin/python3
?同時存在,會優先使用?/usr/local/bin/python3
。 -
如果?
~/bin/ls
?用戶自定義腳本和程序)存在,它會覆蓋系統的?/usr/bin/ls(
系統核心命令和包管理器安裝的軟件)
(不建議這樣做)。
-
2.?sbin
?vs?bin
-
bin
(binary):普通用戶可用的命令(如?ls
、python3
)。 -
sbin
(system binary):系統管理命令,通常需要?sudo
(如?iptables
、useradd
)。
3. 用戶級目錄
-
~/.local/bin
-
由?
pip install --user
、cargo install
、npm install -g
(部分情況)等工具使用。 -
如果命令無法運行,可能是?
~/.local/bin
?不在?PATH
?中(但你的環境已經包含)。
-
-
~/bin
-
用戶自定義目錄,適合存放個人腳本(如?
mybackup.sh
)。 -
如果不存在,可以手動創建:
mkdir -p ~/bin echo 'export PATH=$PATH:~/bin' >> ~/.bashrc source ~/.bashrc
-
????????由于ls命令本身位于PATH的某個路徑下,因此即使不指定完整路徑,系統也能成功找到并執行該命令:
如何讓我們的可執行程序也能不帶路徑直接運行?
有兩種方法可以實現:
4、方法一:將可執行程序拷貝到環境變量PATH的某一路徑下
????????因為系統會默認搜索PATH中的路徑,所以只需把可執行文件放到PATH包含的任意目錄下,之后就可以直接輸入程序名運行了。
sudo cp project /usr/bin
5、方式二:將可執行程序所在的目錄導入到環境變量PATH當中
????????將可執行程序所在的目錄導入到環境變量PATH當中,這樣可以當沒有指定路徑時,系統就會來到該目錄下進行查找:
export PATH=$PATH:/home/hmz
????????將可執行程序所在的目錄導入到環境變量PATH當中后(我們對比PATH原來和現在的樣子,找出不同點),位于該目錄下的可執行程序也就可以在不帶路徑的情況下執行了:
6、關于export PATH=$PATH:/home/hmz的一些小知識
????????這個命令的作用是將?/home/hmz
?目錄添加到系統的?PATH
?環境變量中,使得在該目錄下的可執行文件可以在任何位置直接運行,而不需要輸入完整路徑。
具體解析
-
export:
這是一個 shell 命令,用于設置或導出環境變量,使其對當前 shell 及其子進程生效。 -
PATH:PATH
?是一個環境變量,存儲了一系列目錄路徑,系統會在這些路徑中查找可執行文件。多個路徑之間用?:
?分隔。 -
$PATH:$PATH
?表示當前?PATH
?變量的值。例如,默認情況下,PATH
?可能包含?/usr/local/bin:/usr/bin:/bin
?等路徑。 -
:/home/hmz:
這里將?/home/hmz
?追加到?PATH
?變量的末尾,用?:
?分隔。 -
PATH=$PATH:/home/hmz:
這個賦值語句將新的?PATH
?值設置為原來的?PATH
?加上?:/home/hmz
。 -
export PATH=$PATH:/home/hmz:
最終,export
?將這個修改后的?PATH
?變量導出,使其生效。
這樣,當輸入一個命令時,shell 會依次在下面的路徑中查找可執行文件。
注意事項(重要!!!)
-
臨時生效:這種方式只在當前 shell 會話中有效,關閉終端或新開終端后,
PATH
?會恢復默認值。
如果要永久生效,可以將其添加到?~/.bashrc
、~/.bash_profile
?或?/etc/environment
(系統級)中。 -
路徑順序:
PATH
?的查找順序是從左到右的。如果?/home/hmz
?下有與系統命令同名的可執行文件,可能會導致預期外的行為,所以最好不要有與系統命令同名的可執行文件。 -
安全性:不建議將當前目錄?
.
?或普通用戶目錄加入?PATH
,以免惡意程序覆蓋系統命令,造成系統污染。
永久生效的方法(以?~/.bashrc
?為例):
echo 'export PATH=$PATH:/home/hmz' >> ~/.bashrc
source ~/.bashrc # 使更改立即生效
這樣每次打開終端時,PATH
?都會自動包含?/home/hmz
。
????????之后有空再探討其他無需指定路徑即可運行程序的方法,這個超綱了,現在我先學好要學的先,可以先看一下這些方法的表格:
方法 | 適用場景 | 持久性 | 是否需要修改?PATH |
---|---|---|---|
符號鏈接 | 單文件程序 | 永久 | 否(需鏈接到?PATH ?目錄) |
別名 | 簡化長命令 | 需寫入配置文件 | 否 |
函數 | 需要參數處理的復雜命令 | 需寫入配置文件 | 否 |
桌面快捷方式 | GUI 程序 | 永久 | 否 |
環境變量擴展 | 臨時替代長路徑 | 可選 | 否 |
包管理器安裝 | 系統級工具 | 永久 | 自動配置 |
標準化命名 | 個人腳本 | 永久 | 需?PATH ?包含目錄 |
exec ?命令 | 包裝腳本(無子進程開銷) | 永久 | 需?PATH ?包含目錄 |
五、測試HOME
????????每個用戶登錄系統時都會自動進入自己的主工作目錄(家目錄),該路徑存儲在HOME環境變量中。
echo $HOME
1、分別以root和普通用戶執行echo $HOME
,對比輸出差異
普通用戶:
超級用戶:
2、執行cd ~; pwd
,理解~
與$HOME
的關系
????????在Linux操作系統中,~
(波浪號)和$HOME
環境變量之間存在緊密關聯,它們都指向當前用戶的家目錄(Home Directory)。以下是具體解釋和示例:
1.?~
?的作用
-
~
?是一個Shell擴展符號(稱為Tilde Expansion
),默認代表當前用戶的家目錄路徑。-
例如:
cd ~
?等同于?cd /home/username
(假設用戶名是username
)。 -
若在
~
后接用戶名(如~otheruser
),則指向其他用戶的家目錄(如/home/otheruser
)。(之前在cd命令的博客中沒有提及到,現在補充上)
-
2.?$HOME
?的作用
-
$HOME
?是一個環境變量,存儲當前用戶家目錄的絕對路徑。-
可通過?
echo $HOME
?查看其值,通常輸出如?/home/username
。 -
在腳本或命令中直接使用
$HOME
時,Shell會將其替換為實際路徑。
-
3.?~
?與?$HOME
?的關系
-
等價性:在大多數情況下,
~
和$HOME
指向同一路徑。-
執行?
cd ~; pwd
?和?cd $HOME; pwd
?的輸出結果相同。
-
-
差異:
-
~
?是Shell提供的快捷方式,由Shell在解析命令時直接擴展。 -
$HOME
?是環境變量,通過變量替換機制實現。 -
某些場景下(如字符串中未引用的
~
),Shell可能不會展開~
,而$HOME
始終作為變量處理。
-
4.?示例驗證
腳本內容 (compare_home_tilde.sh
)
#!/bin/bash# 比較 ~ 和 $HOME 是否相同
if [ ~ = "$HOME" ]; thenecho "They are identical: ~ points to $HOME"
elseecho "They are different: ~ is '~', but HOME is '$HOME'"
fi
????????關于腳本語言,我會在后續學習中更新,畢竟學會了命令而不會腳本語言有點缺失靈魂的感覺!!!
使用方法
-
將腳本保存為?
compare_home_tilde.sh
-
賦予執行權限并運行:
chmod +x compare_home_tilde.sh ./compare_home_tilde.sh
5.?注意事項
-
引號的影響:
-
雙引號內
$HOME
會被擴展,但~
不會。例如:echo "~" # 輸出 ~ echo "$HOME" # 輸出 /home/username
-
-
腳本可移植性:
-
在腳本中優先使用
$HOME
,因為其行為更明確;~
的展開可能受Shell配置影響。
-
總結
? ? ~
和$HOME
本質上是同一目標(用戶家目錄)的不同表示方式。理解它們的異同有助于在命令行或腳本中更靈活地操作路徑。
六、測試SHELL
????????在Linux操作系統中,我們輸入的各種命令需要通過命令行解釋器執行。Linux支持多種命令行解釋器(如bash、sh),可以通過查看環境變量SHELL來確認當前使用的解釋器類型。
echo $SHELL
該命令行解釋器實際上是系統內置的一條命令,當其運行成為進程后,便能提供命令行解釋功能。
七、環境變量相關命令
1、echo:顯示指定環境變量的值
2、export:設置新的環境變量
1?? 設置臨時環境變量
export MY_TEST="Hello World"
2?? 查看變量值
echo $MY_TEST
輸出:
3?? 驗證是環境變量(對子進程可見)
bash -c 'echo $MY_TEST'
輸出:
命令分解解釋
-
bash:
啟動一個新的 Bash 子 Shell(子進程)。 -
-c:
讓 Bash?執行后面跟著的命令字符串,而不是進入交互模式。 -
'echo $MY_TEST':
在新啟動的 Bash 子進程中運行?echo $MY_TEST
,即打印?MY_TEST
?變量的值。
為什么用這個命令測試?export
?
-
環境變量的核心特性:
使用?export
?設置的變量會傳遞給子進程(如新啟動的 Shell、腳本或其他程序)。
如果直接?echo $MY_TEST
,只能證明變量在當前 Shell 存在,但無法確認它是否真的是環境變量(即是否能被子進程繼承)。 -
這個測試的作用:
通過?bash -c
?啟動一個全新的子 Shell,檢查?MY_TEST
?是否能被子進程訪問:-
如果輸出?
Hello World
?→ 說明?export
?成功(變量是環境變量)。 -
如果無輸出 → 說明變量未被?
export
(只是當前 Shell 的局部變量)。
-
4?? 刪除環境變量(下面會介紹)
unset MY_TEST
5?? 驗證已刪除
echo $MY_TEST
(無輸出,變量已不存在)
3、env:顯示所有環境變量
環境變量說明
環境變量名稱 | 作用描述 |
---|---|
XDG_SESSION_ID | 當前用戶會話的唯一標識符,通常由系統管理工具(如 systemd)分配 |
HOSTNAME | 當前系統的主機名 |
TERM | 終端類型(如 xterm),用于控制終端的行為和顯示方式 |
SHELL | 當前用戶使用的默認 Shell 程序路徑(如 /bin/bash) |
HISTSIZE | Shell 歷史記錄中保存的命令數量上限(如 10000) |
SSH_CLIENT | SSH 客戶端連接信息,格式為 IP 源端口 目標端口(如 183.20.145.59 61461 22) |
SSH_TTY | SSH 連接的終端設備文件路徑(如 /dev/pts/1) |
USER | 當前登錄的用戶名(如 hmz) |
LD_LIBRARY_PATH | 動態鏈接庫(.so 文件)的額外搜索路徑,優先級高于系統默認路徑 |
LS_COLORS | 定義 ls 命令輸出中不同文件類型的顯示顏色(如目錄、壓縮包、圖片等) |
當前用戶的郵件存儲路徑(如 /var/spool/mail/hmz) | |
PATH | 系統查找可執行文件的路徑列表,用冒號分隔 |
PWD | 當前工作目錄的路徑(如 /home/hmz) |
LANG | 系統語言和字符編碼設置(如 en_US.UTF-8) |
HISTCONTROL | 控制 Shell 歷史記錄的存儲方式(如 ignoredups 表示忽略重復命令) |
SHLVL | Shell 層級,表示當前 Shell 嵌套深度(初始為 1,每啟動一個子 Shell 加 1) |
HOME | 當前用戶的主目錄路徑(如 /home/hmz) |
LOGNAME | 登錄用戶名(通常與 USER 相同) |
SSH_CONNECTION | SSH 連接的詳細信息,包括客戶端和服務端的 IP 及端口 |
LESSOPEN | 指定 less 命令的預處理腳本(如?/usr/bin/lesspipe.sh %s ) |
XDG_RUNTIME_DIR | 用戶運行時文件(如套接字、PID 文件)的存儲目錄(如 /run/user/1000) |
HISTTIMEFORMAT | 定義 Shell 歷史記錄中時間戳的格式(如 %F %T hmz 表示年-月-日 時:分:秒) |
4、unset:刪除環境變量
1. 添加測試環境變量
export TEST_VAR="Hello, this is a test variable"
2. 驗證變量是否存在
echo $TEST_VAR
輸出:
3. 使用?unset
?清除變量
unset TEST_VAR
4. 再次驗證變量
echo $TEST_VAR
輸出:(空,因為變量已被刪除)
5. 檢查是否從環境變量中移除
env | grep TEST_VAR
輸出:(空,表示環境變量列表中已不存在)
關鍵說明:
-
export
?將變量提升為環境變量(對子進程可見) -
unset
?徹底刪除變量(無論是普通變量還是環境變量) -
測試時建議用新變量名(如?
TEST_VAR
),避免誤刪重要變量
5、set:顯示本地定義的Shell變量和環境變量
? ? set
?命令不能直接添加環境變量,它的主要用途是?設置Shell內部選項?或?顯示所有變量(包括環境變量和局部變量)。
1.?查看所有變量(快速測試)
# 設置一個普通變量
TEST_VAR="hello"# 用 set 查看變量(過濾顯示 TEST_VAR)
set | grep TEST_VAR
輸出:
2.?開啟/關閉調試模式
# 開啟調試(顯示每條執行的命令)
set -x# 執行一個命令(會看到詳細輸出)
echo "This is a test"# 關閉調試
set +x
輸出:
📌?總結
-
set
?直接使用?→ 顯示所有變量(內容很多,建議用?grep
?過濾)。 -
set -x
?→ 調試腳本時顯示命令執行過程。 -
set +x
?→ 關閉調試。
八、環境變量的組織方式
在系統當中,環境變量的組織方式如下:
????????每個程序都會接收一個環境變量表,該表是一個字符指針數組,每個指針指向一個以'\0'結尾的環境字符串,最后一個字符指針為空。
九、main函數的參數(超重點!!!)
????????在C和C++編程中,main
函數是程序的入口點,它可以接收來自命令行的參數。main
函數的參數通常有兩種形式:
1、無參數形式
int main(void)
{// 程序代碼return 0;
}
這種形式表示?main
?函數不接受任何參數。
2、帶參數形式
int main(int argc, char *argv[])
{// 程序代碼return 0;
}
或等價的:
int main(int argc, char **argv)
{// 程序代碼return 0;
}
參數解析:
-
argc
(Argument Count)-
表示命令行參數的數量(包括程序名本身)。
-
類型是?
int
。 -
例如:運行?
./program arg1 arg2
,argc
?的值為 3(./program
、arg1
、arg2
)。
-
-
argv
(Argument Vector)-
是一個指向字符串數組的指針,存儲了所有的命令行參數。
-
argv[0]
?是程序名稱(可能包含路徑)。 -
argv[1]
?到?argv[argc-1]
?是用戶輸入的參數。 -
argv[argc]
?是?NULL
(C標準保證)。
-
示例代碼:
#include <stdio.h>int main(int argc, char *argv[])
{printf("程序名稱: %s\n", argv[0]);printf("參數個數: %d\n", argc - 1);for (int i = 1; i < argc; i++) {printf("參數 %d: %s\n", i, argv[i]);}return 0;
}
運行示例:
假設編譯后的程序名為?demo
,運行:
./demo hello world 123
輸出:
圖中的參數(argv
?數組)是由?Shell 和操作系統?在程序啟動時自動切分的。具體過程如下:
1. 參數切分的參與者
-
Shell(命令行解釋器):
當用戶在終端輸入命令(如?./demo hello world 123
)時,(父進程)Shell 會按空格將字符串拆分成多個單詞(./demo
、hello
、world
、123
)。 -
操作系統(內核):
當 Shell 調用?execve()
?等系統調用啟動程序時,內核會將拆分后的參數以?char* argv[]
?數組的形式傳遞給新程序的入口(如?main
?函數)。
2. 關鍵細節
-
空格作為分隔符:
Shell 默認按空格切分參數。若參數包含空格,需用引號包裹(如?./demo "hello world"
)。 -
argv[0]
?的特殊性:
通常是程序名(./demo
),但可通過?execve()
?修改(如符號鏈接調用時可能不同)。 -
NULL
?結尾:
方便遍歷時確定參數列表的結束(類似 C 字符串的?\0
)。
3、擴展:envp
(環境變量,非標準)
某些編譯器(如GCC)支持第三個參數?envp
,用于訪問環境變量:
#include<stdio.h>int main(int argc, char *argv[], char *envp[])
{int i = 0;// envp 存儲環境變量,以NULL結尾for (; envp[i] != NULL; i++) {printf("環境變量 %d: %s\n", i, envp[i]);}return 0;
}
這段代碼用于打印當前進程的所有環境變量:
但更可移植的方法是使用?getenv
?函數(<stdlib.h>
)。(后面會講解)
注意事項:
-
參數順序:
argc
?必須在?argv
?之前。 -
參數名稱:
argc
?和?argv
?是約定俗成的命名,但可以自定義(如?int main(int count, char **args)
)。 -
返回值:通常返回?
0
?表示成功,非零值表示錯誤(由操作系統或調用者處理)。
通過?main
?函數的參數,程序可以靈活地接收外部輸入,實現命令行交互功能。
4、編寫命令選項選擇
我們可以編寫一段簡單的代碼,根據用戶選擇的選項顯示不同的提示信息:
#include <stdio.h>
#include <string.h>int main(int argc, char *argv[], char* envp[])
{if(argc > 1){if(strcmp(argv[1], "-a") == 0){printf("you used -a option...\n");}else if(strcmp(argv[1], "-b") == 0){printf("you used -b option...\n");}else{printf("you used unrecognizable option...\n");}}else{printf("you did not use any option...\n");}return 0;
}
代碼運行結果如下:?
由此,我們可以得到一個結論:
指令選項的實現原理:main的命令行參數,是實現程序不同子功能的方法!!!
十、_start函數與main函數的關系
在C/C++程序中,_start
?和?main
?函數的關系是程序執行流程的關鍵環節。
1、_start:程序的真正入口
-
系統級入口:當操作系統加載程序后,首先執行的是?
_start
(由C運行時庫或鏈接器默認提供),而非直接跳轉到?main
。它是二進制程序的實際入口點(可通過鏈接腳本或編譯器選項修改)。 -
職責:
-
初始化全局變量、堆棧、內存管理等運行時環境。
-
設置命令行參數(
argc
,?argv
)和環境變量。 -
調用?
main
?函數,并將參數傳遞給它。
-
2、main:開發者定義的入口
-
用戶級入口:
main
?是程序員編寫的程序邏輯起點,但它的調用是由?_start
?發起的。 -
執行流程:
_start
?→ 初始化代碼 →?main(argc, argv, envp)
?→ 程序邏輯 → 返回到?_start
?→ 調用?exit
?結束進程。
3、關鍵區別
特性 | _start | main |
---|---|---|
定義者 | 編譯器/運行時庫(如glibc的crt0 ) | 開發者 |
可見性 | 隱藏(通常無需手動實現) | 必須顯式定義 |
返回值 | 直接調用系統退出(如exit ) | 返回值由?_start ?傳遞給系統 |
4、示例流程
// 1. _start (隱藏的初始化)
void _start() {// 初始化環境int argc = ...;char **argv = ...;// 調用mainint ret = main(argc, argv);// 退出程序exit(ret);
}// 2. 用戶編寫的main
int main(int argc, char **argv) {printf("Hello, World!\n");return 0;
}
5. 特殊情況
-
嵌入式系統:可能直接由匯編代碼跳轉到?
main
,跳過部分初始化。 -
繞過main:可通過修改鏈接參數指定其他入口函數(如?
_start
?調用?my_main
?而非?main
)。
6、總結
? ? _start
?是系統與用戶代碼之間的橋梁,負責準備執行環境并啟動?main
;main
?則是開發者控制程序邏輯的入口。兩者共同構成程序的完整生命周期。
十一、通過代碼獲取環境變量
方法一:使用命令行第三個參數
#include <stdio.h>int main(int argc, char *argv[], char *env[])
{for(int i = 0; env[i]; i++) {printf("%s\n", env[i]);}return 0;
}
方法二:通過第三方變量environ獲取
#include <stdio.h>int main(int argc, char *argv[])
{extern char **environ;int i = 0;for(; environ[i]; i++) {printf("%s\n", environ[i]);}return 0;
}
注意:
????????libc中定義的全局變量environ指向環境變量表。由于environ未包含在任何頭文件中,使用時需要用extern聲明。
十二、通過系統調用獲取或設置環境變量
????????除了通過main函數的第三個參數和environ全局變量獲取環境變量外,系統還提供了getenv函數來查詢環境變量。
getenv
?和?putenv
?函數解析
????????在 C 語言中,getenv
?和?putenv
?是用于操作環境變量的兩個標準庫函數(定義在?<stdlib.h>
?中)。它們的作用不同,但都與環境變量的讀取和修改相關。
1.?getenv
?函數(獲取環境變量)
函數原型
char *getenv(const char *name);
功能
-
用于獲取指定名稱(
name
)的環境變量的值。 -
如果環境變量存在,返回其值的字符串指針(
char*
)。 -
如果環境變量不存在,返回?
NULL
。
示例
#include <stdio.h>
#include <stdlib.h>int main()
{const char *path = getenv("PATH");if (path != NULL) {printf("PATH 的值是: %s\n", path);} else {printf("PATH 未設置\n");}return 0;
}
輸出示例
注意事項
-
返回的字符串是只讀的,不能直接修改(否則可能導致未定義行為)。
-
如果環境變量不存在,
getenv
?返回?NULL
,因此使用前應檢查返回值。
2.?putenv
?函數(設置/修改環境變量)
函數原型
int putenv(char *string);
功能
-
用于設置或修改環境變量。
-
參數?
string
?必須是?"NAME=value"
?形式的字符串。 -
成功返回?
0
,失敗返回非?0
(具體實現可能不同)。
示例
#include <stdio.h>
#include <stdlib.h>int main()
{// 設置或修改環境變量if (putenv("MY_VAR=HelloWorld") != 0) {perror("putenv 失敗");return 1;}// 使用 getenv 獲取新設置的環境變量const char *my_var = getenv("MY_VAR");if (my_var != NULL) {printf("MY_VAR 的值是: %s\n", my_var);} else {printf("MY_VAR 未設置\n");}return 0;
}
輸出示例
注意事項
-
putenv
?的參數格式必須是?"NAME=value"
,否則可能不會生效。 -
修改后的環境變量會影響當前進程及其子進程,但不會影響父進程(如 Shell)。
-
在 Windows 上,類似的函數是?
_putenv
(兼容性問題)。
3. 主要區別
函數 | 作用 | 參數格式 | 返回值 | 是否修改環境變量 |
---|---|---|---|---|
getenv | 獲取環境變量的值 | "VAR_NAME" | char* (值)或?NULL | ? 只讀 |
putenv | 設置/修改環境變量 | "VAR_NAME=value" | 0 (成功)或非?0 (失敗) | ? 修改 |
4. 綜合示例
#include <stdio.h>
#include <stdlib.h>int main()
{// 獲取當前 PATHconst char *old_path = getenv("PATH");printf("原 PATH: %s\n", old_path);// 修改 PATH(追加一個新路徑)putenv("PATH=/new/path:/usr/local/bin");// 再次獲取 PATHconst char *new_path = getenv("PATH");printf("新 PATH: %s\n", new_path);return 0;
}
輸出示例
5. 安全注意事項
-
getenv
?返回值不可修改char *path = getenv("PATH"); // path[0] = 'X'; // 錯誤!可能導致程序崩潰
-
putenv
?的字符串必須是持久存儲的char env_str[] = "TMP_VAR=123"; putenv(env_str); // 可以 // 但如果 env_str 是局部變量,函數返回后可能失效
-
跨平臺兼容性
-
Windows 使用?
_putenv
?而不是?putenv
(需?#include <stdlib.h>
)。 -
更安全的替代方案:
setenv
?和?unsetenv
(POSIX 標準,但 Windows 不支持)。
-
6. 替代方案(POSIX)
如果運行在 Linux/macOS(POSIX 環境),可以使用更安全的:
-
setenv("NAME", "value", 1)
:設置環境變量(1
?表示覆蓋,0
?表示不覆蓋)。 -
unsetenv("NAME")
:刪除環境變量。
示例
#include <stdlib.h>
#include <stdio.h>int main()
{setenv("MY_VAR", "Hello", 1); // 設置或覆蓋printf("MY_VAR = %s\n", getenv("MY_VAR"));unsetenv("MY_VAR"); // 刪除printf("MY_VAR = %s\n", getenv("MY_VAR") ? getenv("MY_VAR") : "(null)");return 0;
}
輸出
7、總結
操作 | 推薦函數 | 說明 |
---|---|---|
獲取環境變量 | getenv | 返回?char* ,需檢查?NULL |
設置/修改環境變量 | putenv (Windows:?_putenv ) | 格式?"NAME=value" |
更安全的設置(POSIX) | setenv | 可控制是否覆蓋 |
刪除環境變量(POSIX) | unsetenv | Windows 無直接等效函數 |
十三、環境變量通常具備全局屬性
1、核心知識點
環境變量的全局特性
-
定義:環境變量通過?
export
?導出后,具備全局性,可被當前Shell及其所有子進程(如運行的腳本、編譯的程序)繼承。 -
關鍵規則:
-
子進程會復制父進程的環境變量表(
environ
),但無法反向修改父進程的環境。
-
環境變量具有全局特性,可以被子進程繼承,通過下面的代碼編譯出來的程序可以驗證:
#include <stdio.h>
#include <stdlib.h>int main()
{char *env = getenv("MYENV");if(env) {printf("%s\n", env);}return 0;
}
(1) 初次運行程序無輸出
原因:未通過?export
?設置?MYENV
,該環境變量不存在于當前Shell及其子進程的環境中。
(2) 導出環境變量
export MYENV="hello world" # 設置為全局環境變量
作用:MYENV
?被添加到Shell的環境變量表中,后續所有子進程均可繼承。
(3) 再次運行程序輸出結果
原因:子進程(a.out
)繼承了父Shell(Bash)的環境變量表,getenv("MYENV")
?成功讀取到值。
2、思考現象背后的原理:為什么會出現這種現象?
進程環境變量繼承機制
-
父進程傳遞:當父進程(如Bash)調用子進程(如C程序)時,會將自己的環境變量表(鍵值對數組)復制給子進程。
-
C程序獲取方式:
-
getenv("VAR")
:從子進程的環境變量表中查找指定變量。 -
extern char **environ
:直接訪問環境變量表(全局變量)。
-
為什么需要?export
?
????????普通變量(未?export
)僅存在于Shell進程內部,不會寫入環境變量表,因此子進程無法通過?getenv()
?獲取。
十四、環境變量和本地變量
????????在Bash中,變量分為環境變量和本地變量(也稱為Shell變量),它們的核心區別在于作用域和是否被子進程繼承。以下是詳細對比:
1、作用范圍
類型 | 作用域 | 是否全局 |
---|---|---|
環境變量 | 當前Shell及其所有子進程 | 全局有效(跨進程) |
本地變量 | 僅當前Shell進程內部 | 局部有效(不跨進程) |
2、定義與導出方式
環境變量
-
通過?
export
?命令導出,子進程(如腳本、其他程序)可繼承:export MY_ENV_VAR="value" # 定義為環境變量
-
或分兩步:
MY_ENV_VAR="value" export MY_ENV_VAR
本地變量
-
直接賦值,僅當前Shell可用:
MY_LOCAL_VAR="local_value" # 定義為本地變量
3、子進程繼承性
類型 | 子進程是否可見 | 示例驗證 |
---|---|---|
環境變量 | ? 是 | bash -c 'echo $MY_ENV_VAR' |
本地變量 | ? 否 | bash -c 'echo $MY_LOCAL_VAR' |
示例:
export ENV_VAR="global"
LOCAL_VAR="local"# 子進程(新bash)測試
bash -c 'echo "子進程環境變量: $ENV_VAR"; echo "子進程本地變量: $LOCAL_VAR"'
輸出:
4、常見用途
-
環境變量:
-
配置程序運行環境(如?
PATH
,?HOME
)。 -
在腳本間傳遞參數。
-
-
本地變量:
-
臨時存儲腳本內部使用的數據。
-
避免污染子進程環境(如循環計數器)。
-
5、查看與刪除
-
查看所有變量:
set # 顯示所有變量(包括本地和環境) env # 僅顯示環境變量
-
刪除變量:
unset MY_VAR # 刪除變量(無論環境或本地)
6、擴展知識
-
持久化環境變量:
若需永久生效,需寫入Shell配置文件(如?~/.bashrc
?或?~/.profile
)。 -
函數中的局部變量:
在函數內使用?local
?聲明變量,作用域僅限于函數:func() {local LOCAL_IN_FUNC="secret"echo $LOCAL_IN_FUNC }
7、總結
-
環境變量是“全局護照”,子進程可繼承;本地變量是“臨時便簽”,僅限當前Shell。
-
通過?
export
?控制變量的可見性,合理使用兩者能有效管理腳本的執行環境。
十五、關于內置命令的必知知識
????????在 Shell 中,內置命令(Built-in Commands)是直接集成在 Shell 解釋器(如 Bash、Zsh 等)中的命令,無需調用外部程序或創建子進程即可執行。
1、內置命令的特點
-
不創建子進程
直接由當前 Shell 進程執行,效率更高(例如?cd
?需要改變當前 Shell 的工作目錄,若通過子進程執行則無法生效)。 -
操作本地變量和環境變量
可以直接讀取或修改當前 Shell 的變量(如?set
,?export
,?read
)。 -
無需外部二進制文件
不依賴?PATH
?環境變量,即使系統文件損壞也能使用。 -
優先級高于外部命令
當內置命令與外部命令同名時,優先執行內置命令(可通過?command
?或完整路徑調用外部命令)。
2、常見內置命令分類
1. 基礎操作
命令 | 功能描述 | 示例 |
---|---|---|
cd | 切換工作目錄(必須內置,否則無法影響當前 Shell) | cd /tmp |
echo | 輸出字符串(支持?-e ?解析轉義符等特性) | echo -e "Line1\nLine2" |
pwd | 打印當前目錄(內置版本可避免符號鏈接問題) | pwd -P (顯示物理路徑) |
read | 從標準輸入讀取數據并賦值給變量 | read -p "Name: " username |
2. 變量管理
命令 | 功能描述 | 示例 |
---|---|---|
set | 設置/顯示 Shell 變量和選項(如?set -x ?開啟調試模式) | set -o vi (啟用 vi 編輯模式) |
unset | 刪除變量或函數 | unset PATH |
export | 將變量提升為環境變量(供子進程使用) | export VAR=value |
local | 在函數內定義局部變量(僅限函數作用域) | local var="temp" |
3. 流程控制
命令 | 功能描述 | 示例 |
---|---|---|
exit | 退出當前 Shell 或腳本(可指定退出狀態碼) | exit 1 (異常退出) |
return | 從函數中返回(可帶狀態碼) | return 0 (函數成功返回) |
source /. | 在當前 Shell 執行腳本(不創建子進程,影響當前環境) | source ~/.bashrc |
alias | 定義命令別名 | alias ll='ls -l' |
unalias | 刪除別名 | unalias ll |
4. 作業控制
命令 | 功能描述 | 示例 |
---|---|---|
jobs | 查看后臺運行的作業列表 | jobs -l (顯示 PID) |
fg | 將后臺作業切換到前臺繼續運行 | fg %1 (恢復作業 1) |
bg | 將暫停的作業放到后臺運行 | bg %2 (后臺運行作業 2) |
wait | 等待指定作業或所有后臺作業完成 | wait %1 (等待作業 1 完成) |
3、如何判斷命令是否為內置?
使用?type
?命令檢測:
type cd # 輸出: cd is a shell builtin
type ls # 輸出: ls is /usr/bin/ls (外部命令)
4、與外部命令的區別
特性 | 內置命令 | 外部命令 |
---|---|---|
執行方式 | 由 Shell 直接解釋 | 需創建子進程,通過?PATH ?查找二進制文件 |
變量操作 | 可修改當前 Shell 環境 | 僅繼承環境變量,無法修改父 Shell 變量 |
速度 | 更快(無進程創建開銷) | 相對較慢 |
依賴項 | 不依賴外部文件 | 依賴系統二進制文件 |
5、注意事項
-
特殊的內置命令
某些命令既有內置實現也有外部實現(如?echo
、printf
)。內置版本可能支持擴展功能,而外部版本更符合 POSIX 標準。強制調用外部版本:/bin/echo "Hello" # 顯式調用外部命令 command echo "Hello" # 繞過別名和內置命令
-
腳本可移植性
內置命令的行為可能因 Shell 不同(如 Bash 和 Dash)而有差異,需測試兼容性。 -
性能敏感場景
在循環中優先使用內置命令(如?(( i++ ))
?替代?expr i + 1
)。