Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
18
Help us understand the problem. What is going on with this article?
@TsuyoshiUshio@github

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

More than 1 year has passed since last update.

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

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

調査の背景

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

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

nohup コマンド

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

実行例

hello.sh

#!/bin/bash

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

nohup なし

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

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

$ ps -ef | grep hello

死んでいます。

nohup あり

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

ターミナル殺す

$ 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をしてもプロセスが死ぬ場合があります。これは、SIGCONTSIGTERMSIGHUPではないので受け付けてしまうからです。Runningのプロセスの場合は、これらのシグナルを受信することはありません。

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

Linux のプロセスのステータス

Linux のプロセスのステータスを調べてみましょう。
* 9. Process States

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

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

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

$ 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

シグナルの種類

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

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

nohupまとめ

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

リダイレクト

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

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

std_in_out.sh

#!/bin/bash

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

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

>

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

$ ./std_in_out.sh 
stdout
stdout
$ ./std_in_out.sh > stdout.log
stderr
# cat stdout.log 
stdout

ですので、結果として、標準出力のみがファイルに出力されて、エラー出力に関してはディスクリプタを変更していないので、そのまま画面に表示されています。

数字>

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

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

&>

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

$ ./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に代入するというコマンド。つまり、このリダイレクトは、未定義のディスクリプタ3に標準入力(のコピー、つまり、その後、1が変更されても影響を受けない)が代入される。次に、1に2が代入されて、最終的に3が2に代入される。つまり、標準出力と、標準エラー出力が入れ替わるというものだ。

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

ちなみに、ここで出てきている{ } の記述は複合コマンドという文法で、複数のコマンド実行を一つの標準出力、エラー出力にまとめるというものだ。

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がのちに変更されても、その値は影響を受けない。

$ ./std_in_out.sh 3>&1 1> stdout.log 2>&3
stderr
$ cat stdout.log 
stdout
18
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
TsuyoshiUshio@github
プログラマ。自分の学習用のブログです。内容は会社とは一切関係ありません。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
18
Help us understand the problem. What is going on with this article?