Shell-Script

為什麼 SIGINT 不能在腳本中的後台程序上工作?

  • November 16, 2021

我在腳本中有以下內容:

yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
ps aux | grep yes

當我執行它時,輸出顯示它yes在腳本結束時仍在執行。但是,如果我以互動方式執行命令,則程序成功終止,如下所示:

> yes >/dev/null &
[1] 9967
> kill -INT 9967
> ps aux | grep yes
sean ... 0:00 grep yes

為什麼 SIGINT 會終止互動式實例中的程序,而不是腳本實例中的程序?

編輯

以下是一些可能有助於診斷問題的補充資訊。我編寫了下面的 Go 程序來模擬上面的腳本。

package main

import (
   "fmt"
   "os"
   "os/exec"
   "time"
)

func main() {
   yes := exec.Command("yes")
   if err := yes.Start(); err != nil {
       die("%v", err)
   }

   time.Sleep(time.Second*2)

   kill := exec.Command("kill", "-INT", fmt.Sprintf("%d", yes.Process.Pid))
   if err := kill.Run(); err != nil {
       die("%v", err)
   }

   time.Sleep(time.Second*2)

   out, err := exec.Command("bash", "-c", "ps aux | grep yes").CombinedOutput()
   if err != nil {
       die("%v", err)
   }
   fmt.Println(string(out))
}

func die(msg string, args ...interface{}) {
   fmt.Fprintf(os.Stderr, msg+"\n", args...)
   os.Exit(1)
}

我將它建構為腳本main./main在腳本中執行,並以互動方式執行並提供相同的以下輸出./main./main &

sean ... 0:01 [yes] <defunct>
sean ... 0:00 bash -c ps aux | grep yes
sean ... 0:00 grep yes

但是,./main &在腳本中執行會給出以下結果:

sean ... 0:03 yes
sean ... 0:00 bash -c ps aux | grep yes
sean ... 0:00 grep yes

這讓我相信差異與 Bash 自己的作業控制無關,儘管我在 Bash shell 中執行所有這些。

使用什麼 shell 是一個問題,因為不同的 shell 處理作業控制的方式不同(並且作業控制很複雜;job.c目前bash根據 3,300 行 C 語言cloc)。pdksh例如,Mac OS X 10.11 上的5.2.14 與bash3.2 顯示:

$ cat code
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
$ bash code
38643
38643
$ ksh code
38650
$ 

這裡也相關的是不yes執行信號處理,因此繼承了從父 shell 程序繼承的任何內容;相比之下,如果我們確實執行信號處理——

$ cat sighandlingcode 
perl -e '$SIG{INT} = sub { die "ouch\n" }; sleep 5' &
pid=$!
sleep 2
kill -INT $pid
$ bash sighandlingcode 
ouch
$ ksh sighandlingcode 
ouch
$ 

— SIGINT 被觸發,無論父 shell,因為perl這裡不像yes改變了信號處理。有一些與信號處理相關的系統呼叫,可以通過 DTrace 或strace在 Linux 上觀察到:

-bash-4.2$ cat code
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
pkill yes
-bash-4.2$ rm foo*; strace -o foo -ff bash code
21899
21899
code: line 9: 21899 Terminated              yes > /dev/null
-bash-4.2$ 

我們發現這個yes過程最終SIGINT被忽略:

-bash-4.2$ egrep 'exec.*yes' foo.21*
foo.21898:execve("/usr/bin/pkill", ["pkill", "yes"], [/* 24 vars */]) = 0
foo.21899:execve("/usr/bin/yes", ["yes"], [/* 24 vars */]) = 0
foo.21903:execve("/usr/bin/pgrep", ["pgrep", "yes"], [/* 24 vars */]) = 0
foo.21904:execve("/usr/bin/pkill", ["pkill", "yes"], [/* 24 vars */]) = 0
-bash-4.2$ grep INT foo.21899
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7f18ebee0250}, {SIG_DFL, [], SA_RESTORER, 0x7f18ebee0250}, 8) = 0
--- SIGINT {si_signo=SIGINT, si_code=SI_USER, si_pid=21897, si_uid=1000} ---
-bash-4.2$ 

用程式碼重複這個測試,perl你應該看到SIGINT沒有被忽略,或者也pdksh沒有像bash. 像在互動模式中一樣打開“監視模式”時bashyes被殺死。

-bash-4.2$ cat monitorcode 
#!/bin/bash
set -m
pkill yes
yes >/dev/null &
pid=$!
echo $pid
sleep 2
kill -INT $pid
sleep 2
pgrep yes
pkill yes
-bash-4.2$ ./monitorcode 
22117
[1]+  Interrupt               yes > /dev/null
-bash-4.2$ 

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