SIGINTがスクリプトのバックグラウンドプロセスで機能しないのはなぜですか?

SIGINTがスクリプトのバックグラウンドプロセスで機能しないのはなぜですか?

私のスクリプトには次のものがあります。

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独自のジョブ制御にはほとんど差がないと思いました。

ベストアンサー1

どのシェルを使うかは疑問です。異なるシェルがジョブ制御を異なる方法で処理するためです(そして、ジョブ制御は複雑です。Cによると、現在のCの重量は3,300行ですjob.c)。たとえば、Mac OS X 10.11の5.2.14と3.2は次のように表示されます。bashclocpdkshbash

$ 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信号処理が実行されないため、親シェルプロセスから継承されたすべてのエントリが継承されることです。

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

- 信号処理が変更されたperlのと同じではないため、親シェルが何であれ、SIGINTがトリガされます。 DTrace経由またはここでLinuxで観察yesできる信号処理に関連するいくつかのシステムコールがあります。strace

-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確認できます。 「モニターモード」がオンの場合は、インタラクティブモードと同じように死にます。SIGINTpdkshbashbashyes

-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$ 

おすすめ記事