shellのtrapについて覚え書き

# trap コマンドとは
実行中のプロセスに対するシグナルを検知し、指定された処理を返すコマンドです。
・・・と、言ってもよくわからないと思います。
特に「シグナル」。

**シグナル とは**
実行中のプロセスに対して、特定のイベントを通知するために送出されるものです。</dd>
よく使用されるのは、プロセスを終了するためのシグナルである SIGINT や SIGKILL です。
シグナル送出に良く利用するのは、`kill` コマンドと、ショートカットで利用する `Ctrl + C` ではないでしょうか。

```bash
kill -9 [Pid]
```

上記のコマンドは、プロセスの強制終了などで利用した事があると思います。
上記の強制終了の `kill` コマンドは、オプションで `-9` を指定しています。
`-9` は `SIGKILL` を意味し、指定したプロセスに対し強制終了のシグナルを送出します。
プロセスがこのシグナルを送られると、どんな状態であろうと強制終了させられます。
因みに、オプションを指定しない `kill` コマンドには `-15(SIGTERM:プロセス終了シグナル)` が指定されています。
`Ctrl + C` は、Linux の処理の最中に押下する事で、その処理を中断させることが可能です。
送出しているシグナルは `SIGINT` で、これはプロセスに割り込みを通知します。
`SIGINT` は　シグナル番号 `-2` を意味します。
(指定したプロセスIDに割り込み、処理を強制的に `Ctrl + C` に書き換える事で終了させているのでは？と考えています。この辺りは後々調べます。)

**trap の書式**

```bash
trap [-lp] [[arg] sigspec ...]
```

`trap` はシグナルを送出した際に、指定したコマンドを実行させる事ができます。
書式は以下のようになっています。

```bash
trap 'コマンド' [シグナル番号|シグナル名]
```


コマンド例は、以下です。

```bash
trap 'echo trapped.' 2
```
上記のコマンドをCLIで実行後、`Ctrl + C` を押下すると、`trap` 内で宣言された `echo trapped` が実行されます。

```bash
$ trap 'echo trapped.' 2
# Ctrl + C を押下
$ ^Ctrapped.
```

`-l` オプションを指定すると、使用可能なシグナル一覧が表示されます。

```bash
$ trap -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
```
上記のシグナル一覧は `kill -l` でも確認ができます。

`-p` オプションを指定すると、現在 `trap` で指定した処理を確認できます。

```bash
$ trap -p
trap -- 'echo trapped.' SIGINT
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
```

`trap` の処理をリセットしたい場合は、`trap [シグナル番号|シグナル名]` で可能です。

```bash
$ trap -p
trap -- 'echo trapped.' SIGINT
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
$ trap 2
$ trap -p
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
```

`trap` コマンドは大体把握できたでしょうか。
さて、このコマンドの活用方法を書いていきます。
<br />
# trap コマンドの活用方法

こちらのコマンド、通常のCLIで使うことは中々無いかも知れません(私に使う機会が無いだけかもしれませんが・・・)。
ただ、シェルスクリプトを書く場合結構便利です。
この `trap` コマンドをスクリプト中に宣言しておくと、処理の途中に `Ctrl + C` を押下して中断した時、指定した処理を実行する事ができます。

貴方は新任のサーバ管理者だとしましょう。
貴方は複数のサーバのディスク使用量を `df` コマンドを使って取得したいと考えています。
そのサーバには何故かsendmailやpostfixといったMTAがインストールされておらず、貴方はCronの存在も全く知りません(何しろ会社に入って数ヶ月です。PCの知識ゼロで、新人研修ではJavaの書き方とLinuxの基本的な操作方法と研修報告書と飲み会に時間を費やしていました)。
貴方は毎日決まった時間帯に**手動**で各サーバのディスク使用量を取得していましたが、ある時シェルスクリプトの存在を知り、今実施している作業が幾らか効率化できるのでは？ と考えました。
また、 `tee` コマンドを使って画面とファイルへ出力しようと考えました。
ついでに `diff` を利用して前回の結果と、今回の結果との比較を出力したいと考えました。
貴方は一生懸命調べながらシェルスクリプトを書き上げました。
簡単に書き出すと以下のようになるかと思います。

```bash:dfResult.sh
#!/bin/bash

today=`date +%y%m%d`
dir=/usr/local/src/srvdf
fileA=`ls -tr ${dir} | tail -n 1`

ssh srv1 "echo ----srv1 disk free result---- ; df -h ; echo ''" | tee srvdf.${today}
ssh srv2 "echo ----srv2 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}
ssh srv3 "echo ----srv3 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}
ssh srv4 "echo ----srv4 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}

mv srvdf.${today} ${dir}/srvdf.${today}

fileB=`ls -tr ${dir} | tail -n 1`

echo "----diff "${fileA} ${fileB}"----"
diff ${dir}/${fileA} ${dir}/${fileB}
```
知識に偏りが見られるのは新人だからです。


さて、 この `dfResult.sh` ですが、処理の最中にスクリプトを中断したい場合どうすればいいでしょうか。
シグナル `(Ctrl + C)` が送出できるのは次の4行です。

```bash:dfResult.sh
ssh srv1 "echo ----srv1 disk free result---- ; df -h ; echo ''" | tee srvdf.${today}
ssh srv2 "echo ----srv2 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}
ssh srv3 "echo ----srv3 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}
ssh srv4 "echo ----srv4 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}

```

`ssh` コマンドで各サーバに接続し、`df -h` で現在のディスク容量を出力します。
その後 `tee` コマンドに渡し、標準出力と `srvdf.YYMMDD` という名前でファイルに出力します。
SSH はパスワード認証という想定です。
その場合、パスワード認証の時に `Ctrl + C` で中断が可能です。
しかし、中断した場合、 `tee` にも既に結果を渡しているので、中断した部分までが出力されます。
最初の `ssh` で中断した場合、何も出力されない為、空のファイルが作成されます。
それでは気持ち悪いですし、既にスクリプトを実行していた場合、空のファイルがカレントディレクトリに残ってしまいます。
(回避策は色々あると思いますが、何分新人なので・・・。)
ではどうするか、となった時に利用できるのが、先述した `trap` コマンドです。

```bash
trap 'rm srvdf.${today} ; exit 1'  1 2 3 15
```
上記のコマンドをスクリプトに追加します。
因みに、各シグナル番号の意味は以下です。

|番号|シグナル名|意味|
|:-:|:------:|:----------------------------------------------------|
| 1 |SIGHUP  |プロセスに再起動を通知する。デーモンのリセットに使用される。|
| 2 |SIGINT  |プロセスに割り込みを通知する。Ctrl+Cを押したときに発生する。|
| 3 |SIGQUIT |プロセスに終了を通知する。Ctrl+\ を押したときに発生する。|
| 15|SIGTERM |プロセスに終了を通知する。killコマンドはデフォルトでこのシグナルを指定している。|
(2以外を指定する理由はわかりません。その内調査します。)

追記箇所は、 `ssh` コマンドの前ならばどこでも大丈夫です。
最初の方の行では、変数を宣言しているので、その後に入れるのが綺麗かもしれません。
追記すると以下のようになります。


```bash:dfResult.sh
#!/bin/bash

today=`date +%y%m%d`
dir=/usr/local/src/srvdf
fileA=`ls -tr ${dir} | tail -n 1`

trap 'rm srvdf.${today} ; exit 1'  1 2 3 15

ssh srv1 "echo ----srv1 disk free result---- ; df -h ; echo ''" | tee srvdf.${today}
ssh srv2 "echo ----srv2 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}
ssh srv3 "echo ----srv3 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}
ssh srv4 "echo ----srv4 disk free result---- ; df -h ; echo ''" | tee -a srvdf.${today}

mv srvdf.${today} ${dir}/srvdf.${today}

fileB=`ls -tr ${dir} | tail -n 1`

echo "----diff "${fileA} ${fileB}"----"
diff ${dir}/${fileA} ${dir}/${fileB}
```

`SIGINT` `SIGHUP` `SIGQUIT` `SIGTERM` のシグナルがプロセスに対して送出された際に、`rm` コマンドが実行され、ファイルが削除されます。
その後、セミコロン `;` で コマンドを連結させ、 `exit 1` を実行しています。
`exit` は終了ステータスを表し、 `exit` コマンドが流れるとスクリプトの処理が終了します。
処理を中断しているので、エラーである1をステータスとして返しています。
これで貴方はシェルスクリプトを完成させることができました。おめでとう！

結構簡単でしかも足早ではありましたが、 `trap` の使い方はなんとなくイメージできたでしょうか。
便利なコマンドだと思うのでぜひ使ってみてくださいね。

<br />
# trap コマンドのまとめ


**trap の書式**

```bash
trap 'コマンド' [シグナル番号|シグナル名]
```

**コマンド例)**

```bash
trap 'echo trapped.' 1 2 3 15
```

```bash
$ trap 'echo trapped.' 1 2 3 15
# Ctrl + C を押下
$ ^Ctrapped.
```

```bash
# シグナル送出時の処理をリセットする場合、 trap signal で実行する。
$ trap 1 2 3 15
# Ctrl + C を押下
$ ^C
```

**trap のオプション**

```bash
trap [-lp]
  -l
    シグナル一覧を表示する。
  -p
    trap で指定した処理を確認できる
```

**オプション例)**

```bash
$ trap -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
```

```bash
$ trap -p
trap -- 'echo trapped.' SIGHUP
trap -- 'echo trapped.' SIGINT
trap -- 'echo trapped.' SIGQUIT
trap -- 'echo trapped.' SIGTERM
trap -- '' SIGTSTP
trap -- '' SIGTTIN
trap -- '' SIGTTOU
```
<br />
この記事が誰かのお役に立てれば幸いです。