bashとCtrlキーで送るシグナル

# TL;DR

* シェル（例えば`bash`）上で`Ctrl-C`などを実行すると、プロセスに対してシグナルが送られる
* プロセスは、受け取ったシグナルに対して応答するだけ
    * 多くのものは、そのまま終了する
* シグナルは、ハンドリングすることができる
    * `SIGKILL`除く
* シェル（というか端末）上で、どんな操作を行うとシグナルを送ったりすることができるのかは、`stty`コマンドで確認できる
    * 必ずしもシグナルが送られるわけではない（`Ctrl-D`など）

ということについて、つらつらと書いてみようかなと。

`Ctrl-C`を使うと実行中のプロセスを停止できたりしますが、あれはシグナルを送ってるんですよ、という話。

`Ctrl-Z`だと、サスペンドになりますね。

`Ctrl-D`だと、`bash`が終了しますね。あれはなんなんでしょう？


# 環境

今回の環境は、こちら。

```shell
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.3 LTS
Release:	18.04
Codename:	bionic

$ uname -a
Linux xxxxx 4.15.0-60-generic #67-Ubuntu SMP Thu Aug 22 16:55:30 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
```

Ubuntu Linux 18.04 LTS。

# シグナルの一覧と、端末で使える制御コマンドの確認

シグナル自体は、`kill`コマンドで確認しましょう。

```shell
$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	
```

現在、各キーでどんなシグナルを送ったりすることができるかは、`stty -a`で確認できます。

```shell
$ stty -a
speed 38400 baud; rows 61; columns 220; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
```

例えば、`intr`は`^C`となっていますが、これが`Ctrl-C`であり`SIGINT`なわけですね。

各項目の意味は、`--help`で確認できます。

```shell
$ stty --help
```

シグナルを送るのは、このあたりですね。

```shell
$ stty --help | grep signal | grep send
   intr CHAR     CHAR will send an interrupt signal
   quit CHAR     CHAR will send a quit signal
   susp CHAR     CHAR will send a terminal stop signal
   [-]hup        send a hangup signal when the last process closes the tty
```


# シグナルをトラップする

試しに、こんなスクリプトを書いてみます。

`handle_signal.sh`

```shell
#!/bin/bash

while true; do
    echo 'sleeping...'
    sleep 1
done
```

実行権限付与。

```shell
$ chmod a+x handle_signal.sh
```

実行。

```shell
$ ./handle_signal.sh 
sleeping...
sleeping...
```

`Ctrl-C`で停止。

```shell
$ ./handle_signal.sh 
sleeping...
sleeping...
sleeping...
sleeping...
^C
```

ここまでは、ふつうだと思います。

では、ここで`trap`を追加してみましょう。`SIGINT`をトラップしてみます。

```shell
#!/bin/bash

trap 'echo "trap SIGINT signal"' SIGINT

while true; do
    echo 'sleeping...'
    sleep 1
done
```

`SIGINT`シグナルを受け取ったら、`echo`を実行するようにしてみました。

`trap`への引数は、以下のとおりです。

```shell
trap 'コマンド' [シグナル名 または シグナルの番号]
```

今回は、シグナル名で指定しています。

すると、`Ctrl-C`で停止しなくなります。

```shell
$ ./handle_signal.sh 
sleeping...
sleeping...
^Ctrap SIGINT signal
sleeping...
sleeping...
^Ctrap SIGINT signal
sleeping...
sleeping...
```

これを停止したかったら、別のターミナルから`kill`することになります。

```shell
$ kill [PID]
```

こうなってしまうのは、`Ctrl-C`で送信する`SIGINT`シグナルをハンドリングするようにしたからですね。

ちなみに、`SIGTERM`をつけてしまうと、`kill`でも停止できなくなります。

```shell
trap 'echo "trap SIGTERM signal"' SIGTERM
```

```shell
$ kill [PID]


sleeping...
trap SIGTERM signal
```

これを停止するには、`SIGKILL`シグナルを送ることになります。

```shell
$ kill -KILL [PID]
```

`SIGKILL`シグナルは、`trap`でハンドリングできません。強制終了シグナルだからです。

# `Ctrl-D`は？

ところで、`Ctrl-D`を入力すると`bash`が終了しますね？

`stty -a`の結果と

```shell
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
```

`--help`を見比べると、どうもシグナルを送るわけではなさそうです。

```shell
$ stty --help | grep signal | grep send
   intr CHAR     CHAR will send an interrupt signal
   quit CHAR     CHAR will send a quit signal
   susp CHAR     CHAR will send a terminal stop signal
   [-]hup        send a hangup signal when the last process closes the tty
```

これの意味は？

```shell
eof = ^D;
```

「入力終了」を表す文字を送ります、と。

```shell
   eof CHAR      CHAR will send an end of file (terminate the input)
```

これで`bash`が終了するようになっています。

`Ctrl-S`を入力すると`bash`が停止したように見えたり、`Ctrl-Q`で再開できるように見えたりするのも、このあたりの情報から読み解けるので、興味があれば見てみるとよいでしょう。