スクリプトには次のものがあります。
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自体のジョブ制御とはあまり関係がないと信じています。
シェルが異なればジョブ制御の処理も異なるため、どのシェルが使用されるかが問題になります(ジョブ制御は複雑です; job.c
in bash
は現在、cloc
に従ってCの3,300行に相当します。たとえば、Mac OS X10.11でのpdksh
5.2.14とbash
3.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
がシグナル処理を実行しないため、親シェルプロセスから継承されるものをすべて継承することです。対照的に、シグナル処理を実行する場合
$ 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
とは異なり、yes
が信号処理を変更したため、SIGINTは親シェルに関係なくトリガーされます。信号処理に関連するシステムコールがあります。これは、DTraceやLinuxの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
コードを使用してこのテストを繰り返すと、SIGINT
が無視されないこと、またはpdksh
の下にbash
。 bash
のインタラクティブモードのように「モニターモード」をオンにすると、yes
が強制終了されます。
-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$