程序的退出狀態(tài)
當一個程序結束時會向父進程報告自己的退出狀態(tài)( exit status ). 通過傳遞 int 類型的變量給庫函數 exit 或系統(tǒng)調用 _exit 可以設置當前程序的退出狀態(tài), 在 Linux 中, 通過 WEXITSTATUS 返回的退出狀態(tài)的值域為 [0, 255] 之間的整數 . 如果傳遞的值不在這個范圍內, 內核會自動幫你強轉為 u_int8_t . 通過 waitpid 庫函數可以得到子進程的退出狀態(tài), 其值存儲在參數 wstatus 的低 8 位中.
// 定義在 wait.h 中
# define WEXITSTATUS(status) __WEXITSTATUS (status)
// 定義在 waitstatus.h 中
/* If WIFEXITED(STATUS), the low-order 8 bits of the status. */
#define __WEXITSTATUS(status) (((status) 0xff00) >> 8)
下面這個例子展示了如何使用 waitpid 及相關宏函數獲取子進程的退出狀態(tài):
#include unistd.h>
#include stdlib.h>
#include stdio.h>
#include sys/types.h>
#include sys/wait.h>
#define PARENT_EXIT 10086
#define CHILD_EXIT -10
int main()
{
pid_t pid = fork();
if (pid > 0)
{
int wstatus;
// 父進程等待子進程執(zhí)行完畢, 用 WUNTRACED 選項追蹤已結束的子進程
pid_t child_pid = waitpid(pid, wstatus, WUNTRACED);
if (WIFEXITED(wstatus))
printf("Child exit status: %d\n", WEXITSTATUS(wstatus));
else
perror("Bad wait status\n");
// 父進程退出
exit(PARENT_EXIT);
}
else if (pid == 0)
{
// 子進程立即退出, 因此需要父進程設置 WUNTRACED
exit(CHILD_EXIT);
}
else
{
// 處理 fork 時出現的錯誤
perror("fork\n");
exit(EXIT_FAILURE);
}
}
編譯并運行上例可以得到被強轉后的狀態(tài)碼, 我們使用 WIFEXITED 判斷等待的子進程是否執(zhí)行成功, 然后對執(zhí)行成功子進程使用 WEXITSTATUS 獲取其退出狀態(tài). 對程序來說, 最終的退出狀態(tài)就是主進程的退出狀態(tài).
> gcc ecitcode.c;./a.out;echo "Parent exit status: $?"
Child exit status: 246 # -10 強轉為 uint8
Parent exit status: 102 # 10086 強轉為 uint8
在 POSIX 標準中規(guī)定退出狀態(tài) 0 代表該程序正常退出, 1 代表發(fā)生錯誤, 其他數字由程序自行規(guī)定, 因此在 glibc 的 stdlib.h 中僅定義了如下宏:
#define EXIT_FAILURE 1 /* Failing exit status. */
#define EXIT_SUCCESS 0 /* Successful exit status. */
程序本身一般會在文檔中事先約定每種退出狀態(tài)代表的退出原因( termination ), 例如在 ls 的幫助文檔中:
> ls --help
...其他內容...
Exit status: # 退出狀態(tài)
0 if OK, # 正常執(zhí)行
1 if minor problems # 次要問題, 例如: 無法訪問子目錄
2 if serious trouble # 嚴重錯誤, 例如: 無法訪問命令行參數
...其他內容...
命令的退出狀態(tài)
在 bash 中會記錄所執(zhí)行命令的退出狀態(tài), 可以通過 $? 獲取最近執(zhí)行的命令的退出狀態(tài). bash 自身的退出狀態(tài)為執(zhí)行的最后一條命令的退出狀態(tài), 也就等價于顯式指定 exit $? . 如果沒有執(zhí)行任何命令就退出, 則 bash 的退出狀態(tài)為 0 , 要注意在 bash 中用 0 表示 true , 用非零表示 false .
# 用 exit 顯式指定退出狀態(tài)
> bash
> exit 98
exit
> echo $?
98
# 什么也不執(zhí)行則退出狀態(tài)為 0
> bash
exit # Ctrl + D 退出
> echo $?
0
# 默認為最后一條命令的退出狀態(tài)
> bash
> ecasd
ecasd: command not found
exit # Ctrl + D 退出
> echo $?
127
在 bash 中對不同種類命令的退出狀態(tài)作出如下規(guī)定:
內置命令: 由于內置命令執(zhí)行時不需要啟動額外的子進程, 因此需要用返回值模擬退出狀態(tài). 每個函數都定義了自己的退出狀態(tài), 例如: 內置命令 source 將腳本文件的最后一個命令的返回狀態(tài)作為命令的返回狀態(tài). bash 中所有的內置命令都用退出狀態(tài) 2 表示用法錯誤, 例如: 選項錯誤, 缺少參數.
> cd -+- # 錯誤的參數
bash: cd: -+: invalid option
cd: usage: cd [-L|[-P [-e]] [-@]] [dir]
> echo $?
2
外部命令: 外部命令的退出狀態(tài)就是使用 waitpid 得到的子進程的退出狀態(tài), 如果子進程在執(zhí)行過程被編號為 N 的信號所終止, 則得到的退出狀態(tài)就為 128+N .
Shell 函數: 定義 shell 函數時, 函數名與之前已定義的只讀函數名相同則退出狀態(tài)為 1 , 當發(fā)生語法錯誤則退出狀態(tài)為 2 . 執(zhí)行 shell 函數時, 函數中最后執(zhí)行的一條命令的退出狀態(tài)就是整個函數的退出狀態(tài).
# 二次定義只讀函數報錯
> func () { echo; }
> readonly -f func
> func; echo $?
0
> func () { echo poi; }
bash: func: readonly function
> echo $?
1
# 定義函數發(fā)生語法錯誤
> fune () {aa}
bash: syntax error near unexpected token '{aa}'
> echo $?
2
# 函數的退出狀態(tài)是最后執(zhí)行的命令的退出狀態(tài)
> funr () { echo; return 6; }
> funr; echo $?
# echo 打印的空行
6 # return 6 是函數中最后執(zhí)行的命令
表達式: 使用 ((...)) 或 let 修飾的表達式的退出狀態(tài)取決于表達式的值, 如果表達式的值為 0 則退出狀態(tài)為 1 ; 如果表達式的值為非零, 則退出狀態(tài)為 0 .
> let 0+0; echo $?
1 # 表達式值為零
> ((7-5)); echo $?
0 # 表達式值非零
命令列表: 用 ; , , , || 連接命令被稱為命令列表, 其中用 和 || 連接的命令使用左關聯( left associativity )模式執(zhí)行列表中的命令. 整個命令列表的退出狀態(tài)為最后一條命令的退出狀態(tài). 此外, $( LISTS ) 以及流程控制結構如: for , while 等的返回狀態(tài)也是結構中的命令列表的退出狀態(tài).
# 功能: 能ping通baidu.com則輸出 `baidu.com is up` , 否則輸出 `baidu.com is down` 。
> ping -c1 baidu.com > /dev/null echo 'baidu.com is up' || echo 'baidu.com is down'
baidu.com is down
> echo $?
0 # 無論是否能 ping 通, 命令列表的退出狀態(tài)都等于最后一條命令的退出狀態(tài)
左關聯模式被廣泛應用于各種語言的邏輯運算符優(yōu)化中. 對于邏輯與運算符 , 以 eq1 eq2 為例, 只有當兩邊都為 True 才會返回 True , 因此當 eq1 為 False 時, eq2 不會執(zhí)行; 對于邏輯或運算符 || , 以 eq1 || eq2 為例, 只要兩邊有一個 True 就會返回 True , 因此當 eq1 為 True 時, eq2 不會執(zhí)行。
腳本: 使用 . 或 source 運行腳本文件等同于在當前 bash 中執(zhí)行代碼塊, 腳本中最后執(zhí)行的命令的退出狀態(tài)就是腳本的退出狀態(tài). 使用 ./腳本名 或 bash 腳本名 的方式執(zhí)行腳本文件等同于執(zhí)行外部命令, 腳本的退出狀態(tài)就是外部命令 bash 的退出狀態(tài). 如果腳本中最后執(zhí)行的命令是 exit , 那么使用 . 或 source 執(zhí)行該腳本文件在執(zhí)行結束后會退出當前 bash .
后臺作業(yè)與協(xié)作進程: 使用不帶選項的 wait 命令可以獲得最后一個執(zhí)行完畢的后臺作業(yè)的退出狀態(tài), 如果使用 wait -n jobsec> 可以獲得指定后臺作業(yè)的退出狀態(tài), 如果作業(yè)不存在則退出狀態(tài)為 127 . 使用 coproc 在 sub shell 中執(zhí)行的命令的退出狀態(tài)和后臺作業(yè)一樣可以被 wait 獲取, coproc 自身的退出狀態(tài)始終為 0 .
> { sleep 10; aad; }
[1] 558
> wait -n 1
[1]+ Exit 127 { sleep 10; aad; }
> coproc { sleep 10; aad; }
[1] 558
> echo $?
0 # 這是 coproc 的執(zhí)行結果
> jobs
[1]+ Exit 127 coproc COPROC { sleep 10; aad; }
管道命令: 默認情況下, 管道的退出狀態(tài)取決于管道中最后一條命令的退出狀態(tài). 如果設置了 set -o pipefail , 那么只有在管道中的全部命令的退出狀態(tài)為 0 時, 整個管道的退出狀態(tài)才為 0 , 否則就是最后一個非零的退出狀態(tài). 在管道前添加 ! 符號可以對整個管道的退出狀態(tài)取反. bash 中的特殊變量 $PIPESTATUS 以數組的形式存儲最近執(zhí)行的前臺管道的退出狀態(tài), 要注意的是單個命令也會被記錄, 也就是說 ${PIPESTATUS[0]} 和 $? 是等價的.
# 管道的退出狀態(tài)是最后一條命令的退出狀態(tài)
> ps | xxp 2>/dev/null | cat; echo $?
0
> set -o pipefail
> ps | xxp 2>/dev/null | cat; echo $?
127 # 設置了 pipefail 因此得到最后一個非零退出狀態(tài)
# 管道中每個命令的退出狀態(tài)被按順序記錄在數組中
> easd 2>/dev/null | ls /nou 2>/dev/null | more 2>/dev/null
> echo ${PIPESTATUS[@]}
127 2 0
# 不帶管道符號的單個命令也會被記錄
> ping asbasdasd 2>/dev/null; echo ${PIPESTATUS[0]}
2
> ping asbasdasd 2>/dev/null; echo $?
2
參考資料
Exit status range
Bash man page
以上就是詳解bash中的退出狀態(tài)機制的詳細內容,更多關于bash 退出狀態(tài) 的資料請關注腳本之家其它相關文章!
您可能感興趣的文章:- StateMachine 狀態(tài)機機制深入解析
- C++有限狀態(tài)機實現計算器小程序
- 一個狀態(tài)機的實現
- 簡單理解Python中基于生成器的狀態(tài)機
- javascript與有限狀態(tài)機詳解
- 淺析C# 狀態(tài)機Stateless