nohupとリダイレクトを自分のために調べてみた

この記事は本当に自分用のメモなので得るものはあまりないと思います。私が最初にリンクを貼っているブログを読まれると最高なので、みなさんはそちらを参照してください。

* [技術/UNIX/なぜnohupをバックグランドジョブとして起動するのが定番なのか？(擬似端末, Pseudo Terminal, SIGHUP他)](https://www.glamenv-septzen.net/view/854)
* [bash: 標準出力、標準エラー出力をファイル、画面それぞれに出力する方法](https://qiita.com/laikuaut/items/e1cc312ffc7ec2c872fc)

師匠のかずきさんのブログエントリに触発されて、私も簡単なことを馬鹿にせず、自分で調べて試してから物事を進めようとおもっています。時間が何倍もかかるのですが、これは最初は仕方ないと思うことにしました。ただ、コントロール出来ている感があるので、やっていくとスピードアップできそうです。

* [問題調査でドツボにはまった状態から抜け出す技術？というか気にしてるところ](https://blog.okazuki.jp/entry/2019/12/15/090621?fbclid=IwAR2c7ki0OYlx2aOXHS46V261cX_aistvo-bUYEE7t-aJ2AXHetF_dG1R6ec)

# 調査の背景

VMをプロビジョンするときに、スクリプトで様々な初期処理をしますが、定番のバックグラウンドジョブをプロビジョニングスクリプトから実行する場合、次のような構成にします。これは前からふわっと知っていますが、正直ちゃんと理解していないので、今回調べてみました。

```bash
$ nohup /foo/var.sh > some.log 1<2&
```

# nohup コマンド
nohup コマンドは、`SIGHUP` がプロセスに送信されても無視するコマンドです。[こちら](https://www.glamenv-septzen.net/view/854)のブログに詳細の解説があるのですが、例えば、あなたがターミナルからコマンドを実行していて、バックグラウンドプロセスとして実行するために、`&`をつけていたとしても、使っていたターミナルを落とすと大体その実行されていたプロセスは死んでしまいます。死ぬかどうかは、シェルや、ターミナルの実装によるのですが、死ぬ可能性が高いです。実際にBashなどは終了時に、バックグラウンドプロセス含め`SIGHUP`を発行してしまいますので、せっかくバックグラウンドで実行していても、死んでしまうことが発生します。ですので、バックグラウンドにするだけではなく、nohupをつけて、`SIGHUP`を無視すると落ちることは無いということです。

## 実行例

_hello.sh_

```bash
#!/bin/bash

while true; do 
    echo "hello still alive"
    sleep 5
done
```

### nohup なし

```bash
$ ./hello.sh > hello.log &
$ cat ./hello.log
hello still alive
hello still alive
```

ターミナルを殺す（私はVSCodeのインテグレーションターミナルでWSLのターミナルを殺しました。）

```bash
$ ps -ef | grep hello
```

死んでいます。

### nohup あり

```bash
$ nohup ./hello.sh > hello.log &
```

ターミナル殺す

```bash
$ ps -ef | grep hello
s -ef | grep hello
ushio     8111     1  0 12:28 ?        00:00:00 /bin/bash ./hello.sh
ushio     8588  2058  0 12:30 pts/1    00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svnhello
```

生きてます。

## プロセスが停止中

ブログによるとバックグラウンドプロセスが停止中の場合は、nohupをしてもプロセスが死ぬ場合があります。これは、`SIGCONT`や`SIGTERM`は`SIGHUP`ではないので受け付けてしまうからです。Runningのプロセスの場合は、これらのシグナルを受信することはありません。

さて、プロセス停止中とはどういう状態でしょうか？

### Linux のプロセスのステータス
Linux のプロセスのステータスを調べてみましょう。
* [9. Process States](https://linuxjourney.com/lesson/process-states)

5つのステータスがあるようです。

1. R: 実行中もしくは、実行可能。CPUからの割り当てを待っている。
2. S: インタラクティブスリープ、イベントを待っている。例えばターミナルからの入力待ちなど
3. D: 非インタラクティブなスリープ。プロセスが殺せなかったり、シグナルで中断されている。大抵はOSのリブートで解消する
4. Z: ゾンビ、殺されたプロセスで、ステータスが収集されるのを待っている
5. T: 停止中、プロセスは、停止されていたり、止められている。

これらは、例えば、`ps aux` の`STAT`のカラムで確認できる。

```bash
$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   8936   312 ?        Ssl  Dec13   0:00 /init ro
root         8  0.0  0.0   8944   228 tty1     Ss   Dec13   0:00 /init ro
ushio        9  0.0  0.0  10660   664 tty1     S    Dec13   0:00 sh -c "$VSCODE_WSL_EXT_LOCATION/scrip
ushio       10  0.0  0.0  10660   712 tty1     S    Dec13   0:00 sh /mnt/c/Users/tsushi/.vscode/extens
ushio       15  0.0  0.0  10660   696 tty1     S    Dec13   0:00 sh /home/ushio/.vscode-server/bin/879
ushio       17  0.0  0.1 971068 30456 tty1     Sl   Dec13   0:35 /home/ushio/.vscode-server/bin/8795a9
ushio       32  0.0  0.0 865420 14532 tty1     Sl   Dec13   0:14 /home/ushio/.vscode-server/bin/8795a9
ushio      157  0.0  0.3 1216168 56644 tty1    Rl   Dec13   1:27 /home/ushio/.vscode-server/bin/8795a9
ushio      170  0.0  0.0  20616  4020 pts/0    Ss   Dec13   0:09 /usr/bin/zsh
ushio      307  0.0  0.0 566720  1148 tty1     Sl   Dec13   0:00 /home/ushio/.vscode-server/bin/8795a9
ushio     2058  0.0  0.0  20088  4416 pts/1    Ss   Dec14   0:03 /usr/bin/zsh
ushio     9777  0.0  0.0  17380  1920 pts/1    R    13:35   0:00 ps aux
```

## シグナルの種類

* [こちら](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/signal.7.html)

先ほどの２つのシグナルは

* `SIGTERM`: 終了シグナル (termination) 
* `SIGCONT`: 一時停止からの再開
* `SIGHUP`: 制御端末のハングアップ検出、もしくは、制御しているプロセスの死

## nohupまとめ

ただし、私が意図しているプロセスは、サーバープロセスなので、停止中という状態は何かおかしいことが発生しない限りならないので、基本`nohup ... &`で、確実に実行可能だと思われます。（すっきり）


# リダイレクト

最初の定番のコードの残りはリダイレクトです。 `> filename 1<2&` をすると、標準出力と、エラー出力を両方ファイルに出力することは知っていますが、文法的にどうなっているのでしょうか？

```bash
$ nohup /foo/var.sh > some.log 1<2&
```

_std_in_out.sh_

```bash
#!/bin/bash

echo "stdout" >&1
echo "stderr" >&2
```

こんな感じで、標準出力と標準エラー出力を単純にアウトプットするシェルを書いてみます。早速リダイレクトがでてきていますが　、まずは、そういう機能であることだけ理解しましょう。

## >

これは、`1>` が省略された形です。標準出力をファイルに出力します。このリダイレクトの本質は、ファイルディスクリプタの代入です。1のファイルディスクリプタをファイルに変更しているわけです。

```bash
$ ./std_in_out.sh 
stdout
stdout
$ ./std_in_out.sh > stdout.log
stderr
# cat stdout.log 
stdout
```
ですので、結果として、標準出力のみがファイルに出力されて、エラー出力に関してはディスクリプタを変更していないので、そのまま画面に表示されています。

## 数字> 

ファイルディスクリプタを指定してリダイレクトします。

```bash
cat stdout.log 1> stdout.log
$ ./std_in_out.sh 1> stdout.log 
stderr
$ cat stdout.log 
stdout
$ ./std_in_out.sh 2> stderr.log
stdout
$ cat stderr.log 
stderr
```

## &> 
標準出力と、標準エラー出力を両方ファイルに出力します。

```bash
$ ./std_in_out.sh &> stdout_error.log
$ cat stdout_error.log 
stdout
stderr
````

## /dev/null
出力を消去します。次の例では、標準エラー出力だけ消去しています。

``bash
$ ./std_in_out.sh 2> /dev/null
stdout
```

## 複数リダイレクト実行

リダイレクトは順番に実行される。`num1>&num2` のコマンドは、ファイルディスクリプタのnum2のコピーをnum1に代入するというコマンド。つまり、このリダイレクトは、未定義のディスクリプタ３に標準入力（のコピー、つまり、その後、１が変更されても影響を受けない）が代入される。次に、１に２が代入されて、最終的に３が２に代入される。つまり、標準出力と、標準エラー出力が入れ替わるというものだ。

```bash
$ { bash ./std_in_out.sh 3>&1 1>&2 2>&3; } 1> stdout.log 2>stderr.log
$ cat stdout.log
stderr
$ cat stderr.log
stdout
```
ちなみに、ここで出てきている`{ }` の記述は複合コマンドという文法で、複数のコマンド実行を一つの標準出力、エラー出力にまとめるというものだ。

* [bashで複数のコマンドを一つのコマンドとして扱う方法(複合コマンド)](https://qiita.com/laikuaut/items/b918e6fa63bdc61f5308)

`num1>&num2` のコマンドの動作を確認しよう。ここで、
1. `3>&1`　(1: 標準出力 2: 標準エラー出力 3: 標準出力)
2. `1> stdout.log` (1: stdout.log 2: 標準エラー出力 3: 標準出力)
3. `2>&3` (1: stdout.log 2: 標準出力 3: 標準出力)

つまり、シェル内の標準出力は、`stdout.log`に書かれるが、エラー出力はそのまま標準出力に出力される。3に代入した、標準出力はコピーであり、1自体もしくは、ポインタではないので、1がのちに変更されても、その値は影響を受けない。

```bash
$ ./std_in_out.sh 3>&1 1> stdout.log 2>&3
stderr
$ cat stdout.log 
stdout
```