Bash

是什麼決定了腳本的後台程序是否獲得終端的 SIGINT 信號?

  • June 22, 2020
#!/usr/bin/env bash

sleep 3 && echo '123' &
sleep 3 && echo '456' &
sleep 3 && echo '999' &

如果我執行它,並SIGINT通過終端按 control-c 發送,它似乎仍然會回顯123...輸出。我認為這是因為它以某種方式分離?

但是,如果我在腳本末尾添加一個wait < <(jobs -p)(等待所有後台作業完成),如果我執行它,然後發送,SIGINT不會顯示123...輸出。

什麼解釋了這種行為?是否wait以某種方式攔截信號並將其傳遞給後台程序?或者它是否與程序是否“連接”到終端的某種狀態有關?

我發現了一個可能與此相關的問題,但我無法弄清楚它與上述行為有何關係:Why SIGCHLD signal was not ignored when using wait() functions?

信號攔截?傳播?不

wait不攔截信號。外殼不通過它。信號既不被截獲也不被傳播。執行您的三個“背景”命令的子shell 要麼直接獲取信號,要麼不獲取信號。

您可以使用以下腳本進行測試:

#!/usr/bin/env bash

printf 'My PID is %s.\n' "$$"
sleep 15 && echo '123' &
sleep 15 && echo '456' &
sleep 15 && echo '999' &
wait < <(jobs -p)

該腳本的行為類似於您的腳本,帶有wait. 我的意思是你可以用+終止它三個“後台”工作。Ctrl``C

再次執行它,不要點擊Ctrl+ C。該腳本將告訴您它自己的 PID ( $$)。現在,如果您SIGINT從另一個終端 ( kill -s INT <pid_here>) 發送,那麼它將終止腳本,但不會終止三個作業。

但是,如果您發送SIGINT到整個程序組 ( kill -s INT -- -<pid_here>),那麼腳本作業將得到它。當您點擊Ctrl+C並且您的終端設置為在擊鍵時發送中斷信號(通常是,stty -a包括intr = ^C)時,也會發生同樣的情況,整個組都會收到信號。


誰得到信號?

這是關於前台程序組的。shell 執行的任務之一是通知終端哪個程序組在前台。

一個終端可能有一個與之關聯的前台程序組。這個前台程序組在處理產生信號的輸入字元中扮演著特殊的角色

$$ … $$. 支持作業控制的命令解釋器程序可以通過將相關程序放置在單個程序組中並將該程序組與終端相關聯來將終端分配給不同的作業或程序組。

$$ … $$

來源

這就是您的情況真正發生的情況:

  1. 最初,您處於互動式外殼中。shell 是它自己的程序組的領導者(程序組 ID 等於 shell 的 PID)。終端將此程序組辨識為前台程序組。
  2. 所述外殼啟用了作業控制。(通常,支持作業控制的 shell 在互動執行時預設啟用它。)當您啟動腳本(或基本上任何非內置腳本)時,shell 使其成為另一個程序組的領導者。終端被告知新程序組現在應該被視為前台程序組。互動式外殼以這種方式將自己置於後台。
  3. 從shebang我們知道它正在bash解釋腳本。非互動式 Bash 預設禁用作業控制。由它執行的命令不會成為它們自己的程序組的領導者(除非命令本身改變了它的程序組;這是可能的,但在你的情況下不會發生)。三個子shell和三個sleep程序與執行腳本屬於同一個程序組。
  4. 當腳本終止時,終端被告知互動式 shell 的程序組現在應該被視為前台程序組(再次)。如果腳本的程序組中的任何程序仍在執行,它將不再位於前台程序組中。

這解釋了您觀察到的行為:

  • 如果在您點擊Ctrl+時腳本正在執行,C那麼它基本上所有的後代都會收到SIGINT. 腳本等待是因為wait還是因為在沒有&. 重要的是它的程序組仍然是前台程序組,並且(孫)子屬於該組。
  • Ctrl如果在您點擊+時腳本不再執行,C則其後代(如果有)將不會收到SIGINT,因為它們不屬於目前前台程序組。

玩轉工作控制

請注意,您可以通過禁用或啟用作業控制來更改此行為。

如果您set +m在互動式 shell 中禁用作業控制 ( ) 並執行腳本,那麼 shell 將執行腳本而不使其成為程序組的領導者。腳本中沒有作業控制。該腳本及其所有子程序基本上都屬於互動式 shell 的程序組。該組將一直是前台程序組。在Ctrl+C所有程序(包括互動式外殼)將收到SIGINT,無論腳本是否仍在執行。

如果您set -m在腳本本身中啟用作業控制 ( ),則三個子 shell 將被放入各自的程序組中。在類似的情況下,沒有的命令&將成為程序組領導,並且終端將被告知新的前台程序組。但是您的命令與&,他們將成為領導者,但前台程序組不會改變。在Ctrl+C他們不會收到SIGINT,無論腳本是否仍在執行,也無論互動式 shell 是否啟用了作業控制。


筆記

  • &分隔符或終止符並不意味著“在後台執行” 。它僅意味著“非同步執行”。執行的命令&可以保留在前台程序組中(例如,如果禁用作業控制)。沒有執行的命令&可以離開前台程序組。
  • 您可能會對互動式 shell在執行命令時將自己置於後台這一事實感到驚訝。這真的發生了。在互動式 Bash 中執行這些命令:
set -m
trap 'echo "Signal received."' INT
sleep 999

Ctrl+C

您將看到sleep被中斷但 shell 沒有收到信號。這是因為外殼sleep在擊鍵時已經放入了一個單獨的程序組,該程序組是前台程序組。shell 不在(當時的)前台程序組中,這意味著它在後台。

現在更改set -mset +m並再次執行:

set +m
trap 'echo "Signal received."' INT
sleep 999

Ctrl+C

禁用作業控制後,sleep將在 shell 的程序組中執行。該組將始終是前台程序組。您將看到來自 的消息trap

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