UNIXの部屋 コマンド検索: リダイレクト


※空白区切りで AND 検索 (例:「ファイル 削除」)

リダイレクト コマンドの出力をファイルや別のコマンドに振り分ける (リダイレクション)

コマンドを実行したとき、その出力をファイルに書き込みたい場合は
% command > file.txt
とする。

コマンドを実行したとき、その出力を別のコマンドの入力として渡したい場合は
% command | command2
とする。これを「パイプで繋ぐ」と言う。
% command | command2 | command3 | command4
と、パイプで多段に接続することもできる。

UNIX では、2つの出力先がある。標準出力と標準エラー出力である。

ls コマンドの出力をファイルに書き込む場合を考えてみよう。拡張子が .txt のファイル名の一覧をファイル output に書き込むには
% ls *.txt > output
とすればよい。しかし拡張子が .txt であるファイルがひとつも存在しなかったらどうなるだろうか。出力先を output にリダイレクトしているにも関わらず、ls コマンドは
% ls *.txt > output
ls: No match.
というエラーを出力する。なぜなら、エラーが出たということはユーザが気づくべきことなので、リダイレクトの指定があったからといって、ファイルに書き込むのは望ましくないからである。

これを実現しているのが、標準出力と標準エラー出力である。ls コマンドはファイル一覧などの通常の出力は標準出力に書き出す。しかし、No match やオプションの誤りなどでエラーが起こった場合は、標準エラー出力に書き出すのである。他のコマンドも ls コマンドと同様に、
  • 普通の出力は標準出力に
  • ユーザに気づいてほしいエラー表示などは、標準エラー出力に
という方針でプログラムが作成されている。

しかし、標準エラー出力もファイルにリダイレクトしたり、パイプで他のコマンドに渡したい場合がある。ここからは csh・tcsh と sh・bash では書式が大きく違うので、分けて記述する。

csh・tcsh の場合
標準出力と標準エラー出力を両方リダイレクトしたい場合は、
% command >& file.txt
% command |& command2
とする。
% command &> file.txt (誤り!)
% command &| command2 (誤り!)
ではないことに注意。これだと、command をバックグラウンドで実行して、その標準出力をファイルや他のコマンドに渡すことになってしまう。

標準出力を file1 に、標準エラー出力を file2 に、などと振り分けるには、
% ( command > file1 ) >& file2
とする。これを応用して、標準エラー出力を全く表示させず、標準出力だけを見るには
% ( command > /dev/tty ) >& /dev/null
とすればよい。例えば、find コマンドで「他人のディレクトリを見る権限がない」という意味の Permission denied というエラー表示が邪魔なときは
% ( find / > /dev/tty ) >& /dev/null
とするとよい。
>> コマンド find *

また、標準エラー出力だけをファイルに書き出すには
% ( command > /dev/null ) >& file
とする。

標準出力だけをページャなどで見たい場合は、普通に
% command | less
として、less 実行中に Ctrl-l で画面を再描画すると、標準エラー出力が消えて見やすくなる。

csh・tcsh の文法は少々腐っているので、標準エラー出力が絡んでくると、トリッキーな技を使う必要がある。

sh・bash の場合
まず、
  • 標準出力は 1 番
  • 標準エラー出力は 2 番
ということを覚えてほしい (ちなみに標準入力は 0 番)。

標準出力を file1 に、標準エラー出力を file2 に、などと振り分けるには、
% command 1>file1 2>file2
とする。
% command 1>/dev/null
% command 2>/dev/null
と、どちらかだけを表示することも簡単に指定できる。上記の find の例だと、
% find / 2>/dev/null
とすればよい。

標準出力と標準エラー出力を両方まとめて他のコマンドに渡すには
% command 2>&1 | less
とし、標準出力と標準エラー出力をまとめて file に書き出す場合は
% command >file 2>&1
とする。ここで順番を逆にして
% command 2>&1 >file (誤り!)
としてはうまくいかないことに注意。

標準エラー出力のみをページャなどで見たい場合は、
% command 2>&1 1>/dev/null | less
である。これも
% command 1>/dev/null 2>&1 | less (誤り!)
と順番を逆にするとうまくいかない。

より複雑な例を紹介しよう。

標準エラー出力のみをパイプに出力する。
% command 2>&1 >/dev/null | command2
標準エラー出力のみをパイプに (出力を閉じるので、command が出力結果をチェックしているならエラーになる)。
% command 2>&1 >&- | command2
標準出力と標準エラー出力を交換する。
% command 3>&1 1>&2 2>&3
標準出力を捨て、標準エラー出力をページャで参照する。
% command 3>&1 >/dev/null 2>&3 | less

さて、先にも述べたように、標準出力と標準エラー出力をファイルに出力する場合は、
% command 1>file 2>&1
% command >file 2>&1 (1 を省略してもよい)
とする。しかし
% command 2>&1 1>file
% command 2>&1 >file (1 を省略してもよい)
は誤りである。なぜ上の書き方が正しいのか、なぜ下の書き方ではダメなのか説明できるだろうか。なお、「リダイレクトは右に書いたものから順に評価されるから」は間違いである。

そもそも「2>&1」という書き方は「2 の出力先を 1 にマージする」というイメージで捉えている人が多いのではないだろうか。「2>&1」の本当の意味は「2 の出力先を、1 の出力先と同じものに設定する」である。

まず、リダイレクトを指定しない場合は、
% command
⇒ 1 の出力先 … 画面
⇒ 2 の出力先 … 画面
となっている。標準出力のみをファイルにリダイレクトする場合は
% command 1>file
⇒ 1 の出力先 … file
⇒ 2 の出力先 … 画面
となる。では標準出力と標準エラー出力をファイル file に出力する
% command 1>file 2>&1
だが、これはまず「1>file」が処理されて、
⇒ 1 の出力先 … file
⇒ 2 の出力先 … 画面
となり、その後に「2>&1」が処理されて、
⇒ 1 の出力先 … file
⇒ 2 の出力先 … file (なぜなら「2 の出力先を、1 の出力先と同じものに設定した」から)
となって、めでたく標準出力と標準エラー出力が file に出力される。

一方、誤った書き方の
% command 2>&1 1>file
であるが、リダイレクト解析前の初期状態は
⇒ 1 の出力先 … 画面
⇒ 2 の出力先 … 画面
となっている。ここでシェルが「2>&1」を処理しても、
⇒ 1 の出力先 … 画面
⇒ 2 の出力先 … 画面
と何も変化しない。なぜなら、「2 の出力先を、1 の出力先と同じものに設定した」からである。その後に「1>file」が処理され、
⇒ 1 の出力先 … file
⇒ 2 の出力先 … 画面
となり、あなたの望んだものとは異なる結果となってしまう。

なお、「n>&m」は「n の出力先を、m の出力先と同じものに設定する」と説明してきたが、UNIX 的には「dup2(m,n)」しているにすぎない。

さて、最後に宿題。以下のシェルスクリプトは何を意図したものでしょうか?
#!/bin/sh
exec 3>&1
status=$({ { command1 3>&- 4>&-; echo $? 1>&4 3>&- 4>&-;} | command2 1>&3 3>&- 4>&-;} 4>&1)
if [ $status != 0 ]; then
...
fi
ちなみに bash であれば $PIPESTATUS を使えば一発で解決である。
>> コマンド csh *   sh *