Shell-Script

是否可以強制忽略信號的程序在 ctrl-C 上退出?

  • May 30, 2021

我有一個忽略 SIGINT 但我想在前台執行的程序。我想找到一種方法來強制它在 Ctrl-C 上關閉。有沒有辦法編寫一個包裝器(你會呼叫./wrapper.sh my_program它)來強制行為不端的程序退出,可能是通過檢測被忽略的 SIGINT 並生成一個 SIGKILL?

這個答案與我正在尋找的完全相反——我想強制一個忽略信號的程序在 SIGINT 上退出。

讓我們建構一個將 SIGINT “轉換”為 SIGKILL 的包裝器。

我們需要一個執行並在+上my_program獲取 SIGINT的程序。當它收到 SIGINT 時,它應該發送 SIGKILL 到. 請注意,我們不需要實際接收由+引起的 SIGINT ;由額外的程序引起的 SIGKILL 就足夠了。Ctrl``c``my_program``my_program``Ctrl``c

相關事實:

  • 沿著另一個程序執行一個程序的標準方法是在後台非同步執行其中一個(即使用 terminating &)。
  • 當您點擊Ctrl+c時,您的終端模擬器將 SIGINT 發送到前台程序組中的程序。
  • 一些(簡單)shell 在同一個程序組中執行所有內容。如果他們被告知在後台執行命令,那麼他們會將其標準輸入重定向到/dev/null或等效文件,以防止命令竊取輸入。
  • 其他 shell 可以在單獨的程序組中執行每個命令。如果他們被告知在後台執行命令,那麼他們將保持其標準輸入不變;後台的命令仍然無法從控制終端竊取輸入,因為SIGTTIN. 這允許 shell 通過通知終端新的前台程序組來將作業從後台移動到前台。該機制稱為作業控制,可以禁用。在腳本中,預設情況下它是禁用的。
  • 無論哪種方式,後台程序都無法從終端讀取。這意味著我們不應該my_program在後台執行,以防標準輸入是終端並且my_program需要從中讀取。
  • 不幸的是,在某些 shell 中,我們也不應該在後台執行其他程序。即使禁用了作業控制,某些 shell 也會使用單獨的程序組。不在前台程序組中的其他程序將不會在Ctrl+上收到 SIGINT c,因此它無法將其“轉換”為 SIGKILL。
  • 在我的 Debian 10posh中是一個 shell,它在同一個程序組中執行所有東西。

這導致以下包裝器:

#!/usr/bin/env posh

( trap 'kill -s KILL 0' INT
 while kill -s 0 "$$" 2>/dev/null; do sleep 1; done
) &
exec "$@"

exec "$@"執行my_program(或您指定的任何內容),可能帶有參數。感謝exec my_program將更換包裝。它不僅可以從可能是終端的標準輸入中讀取;它的 PID 將是被替換的包裝器之一。在這個意義上,包裝是透明的。此外,它還有一個很好的特性:在啟動my_program之前知道PID,my_program我們可以很容易地在其他程序中使用它(在這種情況下,在後台的子 shell 中)來檢測my_program實際終止的時間。

trap轉換” SIGINT 為 SIGKILL。注意kill -s KILL 0將 SIGKILL 發送到整個程序組,包括my_program如果它們在組中的子程序(如果您只想殺死,my_programkill -s KILL "$$"改為使用)。不過只kill -s 0 "$$"測試存在my_program

有一種替代方案不需要在同一程序組中執行所有內容的 shell。訣竅是:管道中的程序應該在一個程序組中執行;通過巧妙的重定向,您可以建構一條不相互連接的管道。

#!/bin/sh -

exec 9>&1
( "$@"; kill -s TERM 0 ) >&9 9>&- | (
 trap 'kill -s KILL 0' INT
 while :; do sleep 1; done
)

在此變體my_program中不會替換包裝器。第二個kill是將SIGINT“轉換”為SIGKILL。第一個kill是終止循環,以防my_program在循環本來可以生存的情況下退出。

很少有場景可能無法按您的預期工作(使用任何包裝器)。其中:

  • 如果my_program分叉並退出,則將真正的工作留給孩子。有問題的麻煩程序很可能不會像這樣,因為你試圖Ctrl+c它。但總的來說可能。
  • 如果my_program在另一個程序組中生成子程序,並且您想將它們連同它們的父程序一起殺死。
  • 如果將終端配置為在+my_program上不發送 SIGINT 。如果您懷疑會發生這種情況,請通過在 and 之間放置來改進包裝器。在這種情況下,程序甚至可能不會忽略 SIGINT,它可能只是確保它不會從終端獲取它。所以也許 SIGKILL 是矯枉過正;也許恢復終端的這個功能就足夠了,+將開始工作。Ctrl``c``stty -F /dev/tty intr ^C``sleep 1``done``Ctrl``c

來自評論:

忽略 SIGINT 的程序通常這樣做是有充分理由的。

真的。嘗試 SIGTERM 或 SIGHUP 而不是 SIGKILL。也許有問題的程序不會忽略其中至少一個,並通過適當的清理優雅地退出。

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