Linux
UNIX

いい加減覚えよう。 `command > /dev/null 2>&1`の意味

質問

  • command > dev/null 2>&1ってよく書いてませんか?
  • crontabでよく書いてませんか?
  • でもそれ、前のcrontabコピペして書いてませんか?
  • 実は意味を理解せずに書いてませんか?

ゴール

  • ファイルディスクリプタを理解する
  • command > /dev/null 2>&1の意味をきちんと理解する

基礎知識

1. ファイルディスクリプタ

Unixには、次の3つの入出力があり、それぞれ番号が振られています。

  • 0: 標準入力
  • 1: 標準出力
  • 2: 標準エラー出力

ファイルディスクリプタとは、これら入出力をOSが判別する為に割り当てられた番号の事です。

2. リダイレクト

よく>という記号を見ると思いますが、これがリダイレクトです。

例えば、標準出力に出すだけのシェルスクリプトを作ってみます。

echo.sh
#/bin/bash

echo "これは標準出力です"

普通に実行すれば標準出力に表示されますが、リダイレクトさせると、別の場所に出力する事が出来るようになります。

ファイルにリダイレクトする場合。

$ ./echo.sh > result.txt
$ cat result.txt
これは標準出力です

3. 特定のファイルディスクリプタにリダイレクトさせる

標準エラー出力にリダイレクトさせる場合はこのようにします。

$ ./echo.sh >&2
これは標準出力です

逆に標準出力にリダイレクトさせる場合はこうです。

$ ./echo.sh >&1
これは標準出力です

今は意味が分からなくてもOKです。
あとで出てます。

4. 出力結果を捨てる

/dev/nullというのはunixのスペシャルファイルの事で、空ファイルの事です。

$ ./echo.sh > /dev/null
$

これまで標準されてたこれは標準出力ですが表示されなかった事が分かります。
これは、標準出力を/dev/nullに捨てた為です。

5. 結果をマージする

先程から出てくる>&{ファイルディスクリプタ}という表現は何かというと、
{ファイルディスクリプタ}に結果をマージするという意味です。

標準出力を標準エラー出力にマージする場合

1>&2

逆に標準エラー出力を標準出力にマージする場合

2>&1

このように使ってきます。

この時点で、もうタイトルの意味が分かってきますね。

タイトルにあったコマンドの意味を考えてみる

command > /dev/null 2>&1

分けて考えましょう。

  • 2>&1
    • 標準エラー出力の結果を標準出力にマージする
  • > /dev/null
    • 標準出力を捨てる

つまり、このコマンドの意味は、

標準エラー出力の結果を標準出力にマージして、/dev/nullに捨てる

という事だったわけです。
(普通標準エラー出力は捨てない方が良いです(cronで動かす場合))

クイズ1. 標準出力を標準エラー出力として表示して下さい

ここまでくれば簡単ですね。
段階的に考えればOKです。

標準出力を標準エラー出力として表示するということは、
表示出力の結果を標準エラー出力の結果にマージすればOKです。

echo.sh
#/bin/bash

echo "これは標準出力を標準エラー出力として表示します" 1>&2

こんな感じですね。

$ ./echo.sh
これは標準出力を標準エラー出力として表示します

このままだと、標準出力か標準エラー出力かわかりませんね。
じゃあクイズ2で確かめて見ましょう。

クイズ2. クイズ1の結果を標準エラー出力と分かるように確認してください

さて、ではここではリダイレクトを使ってみましょう。
先程作ったコードでは、標準エラー出力が表示されているはずなので、
標準エラー出力をファイルにリダイレクトしみます。

まずは、これが標準出力ではないことを確認してみましょう。

$ ./echo.sh > result.txt
これは標準出力を標準エラー出力として表示します
$ cat result.txt
$ 

この場合、下記のようなことが起きてます。

  • 標準出力をresult.txtにリダイレクト
  • 標準エラー出力が表示

じゃあ次に、表示エラー出力であることを確認してみます。

$ ./echo.sh 2>result.txt
$ cat result.txt
これは標準出力を標準エラー出力として表示します

今度はコンソールには何も表示されず、ファイルに残りました。
これが標準エラー出力がresult.txtにリダイレクトされた結果です。

まとめ

  • ファイルディスクリプタが何かを理解出来た
  • &>{ファイルディスクリプタ}や、{ファイルディスクリプタ}&>{ファイルディスクリプタ}の意味も理解出来た
  • 最初に見ていたcommand > /dev/null 2>&1がどういう意味かも分かった

補足

ただし、世間的に2つの話がアリます。

1. そもそもこの記述が分かりづらい

  • まぁ、普通に見て、2>&1とか、1>&2とか、初見ではイミフ
  • 何で/dev/nullはリダイレクトさせてるのに、2>&1は何も書かないの?
  • そもそもどっち先に書くんだっけ

とか色々あります。

これに対して色んな解決索が提示さてますが、1つは標準出力と標準エラー出力を別ファイルにリダイレクトさせるという方法

command 1>normal.txt 2>error.txt

分かりやすいですね。
実際にやってみましょう。
echo.shを少し改造します。

echo.sh
#/bin/bash

echo "これは標準出力です"
echo "これは標準出力を標準エラー出力として表示します" 1>&2

実験

$ ./echo.sh 1>normal.txt 2>error.txt
$ cat normal.txt
これは標準出力です
$ cat error.txt
これは標準出力を標準エラー出力として表示します

想定通りですね。そしてcommand > /dev/null 2>&1よりは分かりやすいです。

もう1つは個人的な意見ですが、cronで動かすようなバッチとかは、
標準出力には出すような内容は、ログファイルに出すようにしておくのが良いかと思ってます。

2. 標準エラー出力を捨てるな

最初のcommand > /dev/null 2>&1というのは、これまで説明してきた通り、
標準エラー出力を標準出力の結果にマージして、/dev/nullに捨てるという事をしてます。

つまり、command(何かしらのスクリプト)で標準エラー出力すらも捨ててるので、
何か問題が起きた時に気づけないという問題があります。

cronで動かす場合は、crontabにMAILTOを書くのが定石ですが、全て> /dev/null 2>&1してしまっていると、このMAILTOには何も送られてこないので意味なくなります。

せめて標準エラー出力が起きたらメールで気付ける。
くらいの運用は最低限出来ておいた方が良いかと思います。

今後

これで、あなたも明日からちゃんと理解しながらcrontabを書ける事になるでしょう。
今のcrontabを見直して、おかしな表現だったり、分かりづらい表現であればチームに提言してみてはいかがでしょうか。