Bash

如何編寫一個可靠地退出(具有指定狀態)目前程序的函式?

  • November 23, 2018

下面的腳本是該問題的最小(儘管是人為的)說明。

## demo.sh

exitfn () {
   printf -- 'exitfn PID: %d\n' "$$" >&2
   exit 1
}

printf -- 'script PID: %d\n' "$$" >&2

exitfn | :

printf -- 'SHOULD NEVER SEE THIS (0)\n' >&2

exitfn

printf -- 'SHOULD NEVER SEE THIS (1)\n' >&2

在這個範例腳本中,exitfn代表一個函式,其工作需要終止目前程序1。

不幸的是,在實施時,exitfn並不能可靠地完成這項任務。

如果執行此腳本,輸出如下所示:

% bash ./demo.sh
script PID: 26731
exitfn PID: 26731
SHOULD NEVER SEE THIS (0)
exitfn PID: 26731

(當然,顯示的 PID 值會因每次呼叫而不同。)

這裡的關鍵點是,在第一次呼叫exitfn函式時,exit 1其主體中的命令無法終止封閉腳本的執行(正如printf緊隨其後的第一個命令的執行所證明的那樣)。相反,在第二次呼叫 時exitfn,此命令確實結束了腳本的執行(正如第二個命令未執行exit 1的事實所證明的那樣)。printf

的兩個呼叫之間的唯一區別exitfn是第一個作為雙組件管道的第一個組件出現,而第二個是“獨立”呼叫。

我對此感到困惑。我曾預計這exit會殺死目前程序(即具有 PID 的程序$$)。顯然,這並不總是正確的。

儘管如此,有沒有一種方法可以編寫exitfn,即使在管道中呼叫它也會退出周圍的腳本?


順便說一句,上面的腳本也是一個有效的 zsh 腳本,並產生相同的結果:

% zsh ./demo.sh
script PID: 26799
exitfn PID: 26799
SHOULD NEVER SEE THIS (0)
exitfn PID: 26799

我也會對 zsh 的這個問題的答案感興趣。


最後,我應該指出,這樣的實現exitfn根本不起作用:

exitfn () {
   printf -- 'exitfn PID: %d\n' "$$" >&2
   exit 1
   kill -9 "$$"
}

…因為在任何情況下,exit 1命令始終是該函式執行的最後一行。(替換 exit 1kill -9 $$不可接受:我想控制腳本的退出狀態,並將其輸出到 stderr。)


1在實踐中,此類函式會在終止目前程序之前執行其他任務,例如診斷日誌記錄或清理操作。

我曾預計這exit會殺死目前程序(即具有 PID 的程序$$

“目前程序”與“PID 給定的程序”不是一回事$$exit退出目前的subshel​​l,如果未在子shell 中呼叫,則退出原始 shell。

某些構造,例如( … )(與子shell 分組)的內容、命令替換($(…))以及管道的每一側(或僅在某些shell 中的左側),在子shell 中執行。子shell 的行為就好像它是用創建的單獨程序一樣fork(),並且通常以這種方式實現(某些shell 在某些情況下不使用子程序,作為性能優化)。子shell有自己的變數副本、自己的重定向等。呼叫exit退出子shell,就像exit()標準C庫中的函式退出程序一樣。有關子shell 的更多資訊,請參閱什麼是子shell(在 make 文件的上下文中)?“子shell”和“子程序”之間的確切區別是什麼?$() 是一個子外殼嗎?.

$$始終是原始 shell 程序的程序 ID。它不會在子shell中改變。一些 shell 有一個在子 shell 中發生變化的變數,例如$BASHPID在 bash 和 mksh 中,或者${.sh.subshell}在 ksh 或$ZSH_SUBSHELLzsh¹$sysparams[pid]中。

有沒有一種方法可以編寫exitfn,即使在管道中呼叫它也會退出周圍的腳本?

不完全是。您需要做更多的工作,要麼在腳本創建子外殼的地方,要麼在頂層,或兩者兼而有之。有關近似值,請參見從子 shell 退出 shell 腳本。


¹ 請注意,儘管與其他 shell 不同,zsh它在父程序(從左到右)的管道中執行擴展,而不是在管道的每個成員中:zsh -c 'echo $ZSH_SUBSHELL | cat'輸出0(即使echo在子程序中執行)和zsh -c 'n=0; echo $((++n)) | echo $((++n))'輸出 2。

引用自:https://unix.stackexchange.com/questions/483744