# 間違い

$ command 2>&1 >/dev/null

# 正しい

$ command >/dev/null 2>&1

- なぜ間違ってしまうのか?

標準エラー出力は2番だから、まず2番を1番が指している先に合流させるために 2>&1 とする。

で、1番を >/dev/null として /dev/null に向ければ、両方とも /dev/null に向く。

- 実際のリダイレクト処理

ls >outfile.txt 2>&1

また実装例から示します。



fd = open("outfile.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);

dup2(fd, 1);

close(fd);

dup2(1, 2);

「ls >outfile.txt 2>&1」が左から処理されていく過程を追います。「>outfile.txt」は上と同じで、ファイルを指すfdが作られた後、1番が一旦クローズされてfdの複製として再生され、ついでにfdがクローズされます。「2>&1」は、2番が一旦クローズされて1番の複製として再生されます。





1番 => [screen]

2番 => [screen]

|

| fd = open();

| dup2(fd, 1);一旦1番をクローズ。

| 1番がfdの複製として再生される。

| close(fd);

V

1番 => [file]

2番 => [screen]

|

| dup2(1, 2);一旦2番をクローズ。

| 2番が1番の複製として再生される。

V

1番 => [file]

2番 => [file]



結局、1番も2番もファイル(outfile.txt)を指すことになります。つまり、lsの標準出力も標準エラー出力もファイルに出力されます。

ls 2>&1 >outfile.txt

いつものように実装例から。



dup2(1, 2);

fd = open("outfile.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);

dup2(fd, 1);

close(fd);

「2>&1」も「>outfile.txt」も、上で説明したとおりの動作をします。左から処理が進むので、処理の順番が異なるだけですが、最終形態も異なります。





1番 => [screen]

2番 => [screen]

|

| dup2(1, 2);一旦2番をクローズ。

| 2番が1番の複製として再生される(結局、画面のまま)。

V

1番 => [screen]

2番 => [screen]

|

| fd = open();

| dup2(fd, 1);一旦1番をクローズ。

| 1番がfdの複製として再生される。

| close(fd);

V

1番 => [file]

2番 => [screen]

(fd => [file])



結局、1番がファイルを指して、2番は画面のままです。

- さらにすごい間違い

$ command >/dev/null &2>1

シェルのリダイレクトについての理解が不十分なためにやってしまった失敗。標準出力も標準エラー出力も /dev/null に捨てたいとき、間違えて以下のようにしてしまうことがときどきあった。最近はやらなくなったが。これだと command の標準出力は /dev/null に向けられるが、command の標準エラー出力は画面に向いてしまう。正しくは以下のように記述する。こうすることで、両方とも /dev/null に向けられる。間違った記述をしてしまう理由は、リダイレクトを誤解しているからだ。そのときの私の思考は以下のようなものだ。ここで問題なのは、2>&1 を「合流」と考えてしまっていることだ。さらに、「2番を1番に合流させているんだから、1番をリダイレクトすれば両者ともに出力先が変わる」と考えてしまっていることだ。実際のリダイレクトはそういった処理はしていない。実際のリダイレクトの処理は、コマンドラインの左から右へ、ファイルディスクリプタをオープン/クローズしながら進められる。以下のサイトの説明を読んだ方が早い。シェルのファイルディスクリプタ操作まずは正しい例。次に、私が間違った例。合流とかそういうものではなく、毎回毎回ファイルディスクリプタのオープンとクローズが行われていることがわかる。私がやった失敗をもう一つ。標準エラー出力を1番のファイルディスクリプタに向けようとしたのだが、この記述だとやりたかったこととはまったく違う結果になる。まず、& が指定されているためコマンドはバックグラウンドで実行される。そして2番の標準エラー出力が 1 というファイルに向けられる。普通ならば「バックグラウンド実行されたよ」という旨のメッセージが表示されるため、すぐに間違いに気づく。しかし、悪いことにこれを crontab に書いていたため、発見が遅れてしまった。cron の結果を確認しようとしたところ、なぜか 1 というファイルができており、そのときになって初めて気づいたのだった。