はじめに
先日、>/dev/null 2>&1は順序が逆だとダメという投稿をしましたが、内容がダメダメだったので、再度まとめ直しました。
ざっくりした理解
ファイルディスクリプタ
入出力インターフェースを識別する識別番号。デフォルトでは下記の通りの設定となっている。
番号 | インターフェース |
---|---|
0 | 標準入力 |
1 | 標準出力 |
2 | 標準エラー出力 |
出力リダイレクト
コマンドの実結果をファイルfile
に書き出す。n
により、出力インターフェースを指定する。(n=1は標準出力、n=2は標準エラー出力)
cmd [n]>file
入力リダイレクト
コマンドへ渡す入力をファイルfile
から読み込む。n
により、入力インターフェースを指定する。(n=1は標準入力)
cmd [n]<file
出力を別の出力へリダイレクトする
n
の出力先をm
と同じに設定する。
cmd [n]>&[m]
ちゃんと調べた結果
ファイルディスクリプタ(IT用語辞典より)
ファイルディスクリプタとは、プログラムがアクセスするファイルや標準入出力などをOSが識別するために用いる識別子。0から順番に整数の値が割り当てられる。OSによってはファイルディスクリプタにバッファ管理機能なども含めた「ファイルハンドル」と呼ばれる管理体系が存在する。
ファイルディスクリプタには、識別子とともにファイル名、ファイルサイズ、プログラムが操作中のファイル内の位置、ファイル作成、更新日時などの情報が含まれており、OSは識別子によってどのファイルを操作するかを判断する。
通常、0:標準入力(stdin)、1:標準出力(stdout)、2:標準エラー出力(stderr)の3つはOS(シェル)が最初に用意するため、プログラムがファイルをオープンすると「3」から順番にディスクリプタが割り当てられる。
プログラム(プロセス)がアクセスする標準入出力やファイルに関連する情報を保持した識別子。保持する情報には、入出力先のファイルパスが含まれる。それぞれの識別子に整数値の識別番号が割り振られる。(ただの識別番号ではなく、ファイルディスクリプタという識別子オブジェクトに識別番号がついているイメージ)
リダイレクト(ちゃんとman bash
を読んでみる)
プロセスの各入出力に対応するファイルディスクリプタを新規作成/複製することにより、入出力先を変更すること。プロセスが生成された時点では、標準入力に対応するファイルディスクリプタが0
に、標準出力に対応するファイルディスクリプタが1
、標準エラー出力に対応するファイルディスクリプタが2
となっている。また、3
以上の番号にファイルディスクリプタを設定することもできる。
入出力リダイレクト
ファイルword
を入力元/出力先とする新規の入力ファイルディスクリプタを作成し、識別番号n
をつける。
cmd [n]>word # Redirecting Output
cmd [n]<word # Redirecting Input
ファイルディスクリプタの複製
識別番号m
のファイルディスクリプタを複製し、識別番号n
をつける。
cmd [n]>&[m] # Duplicating Output File Descriptor
cmd [n]<&[m] # Duplicating Input File Descriptor
【注意】
標準エラーのリダイレクトによると、複製するといいつつ、内部処理としては、n
を示すポインタにm
のポインタを設定している処理のようです。
特別なファイルパス
上記のword
には、ファイルのパスだけでなく、下記の特別なパスが使用できる。
パス | 内容 |
---|---|
/dev/null | 虚無(ここへ向けてリダイレクトすると出力されない) |
/dev/fd/n | 識別番号n で表されるファイルディスクリプタ |
/dev/stdin | 標準入力 |
/dev/stdout | 標準出力 |
/dev/stderr | 標準エラー出力 |
cmd /dev/null 2>&1
の順序問題(再び)
>
はファイルディスクリプタの新規作成、>&
はファイルディスクリプタの複製、として再度cmd /dev/null 2>&1
の順序問題を確認する。
元の設定
+-----+
| 1|-----/dev/stdout
| |
| 2|-----/dev/stderr
+-----+
【誤】cmd 2>&1 >/dev/null
2>&1
で、2
を1
に複製
+-----+
| 1|-----/dev/stdout
| | |
| 2|-------+ (/dev/stderr)
+-----+
>/dev/null
で、新規に1
へ/dev/null
を設定
+--/dev/null
+-----+ |
| 1|--+ /dev/stdout
| | |
| 2|-------+ (/dev/stderr)
+-----+
【正】cmd >/dev/null 2>&1
>/dev/null
で、新規に1
へ/dev/null
を設定
+--/dev/null
+-----+ |
| 1|--+ (/dev/stdout)
| |
| 2|-----/dev/stderr
+-----+
2>&1
で、2
を1
に複製
+--/dev/null
+-----+ | |
| 1|--+ | (/dev/stdout)
| | |
| 2|-------+ (/dev/stderr)
+-----+
>a.txt 2>&1
と>a.txt 2>a.txt
は違う問題
こちらも、再度確認する。
元の設定
+-----+
| 1|-----/dev/stdout
| |
| 2|-----/dev/stderr
+-----+
cmd >a.txt 2>&1
>a.txt
で、新規に1
へa.txt
を設定
+--./a.txt
+-----+ |
| 1|--+ (/dev/stdout)
| |
| 2|-----/dev/stderr
+-----+
2>&1
で、2
を1
に複製
+--./a.txt
+-----+ | |
| 1|--+ | (/dev/stdout)
| | |
| 2|-------+ (/dev/stderr)
+-----+
cmd >a.txt 2>a.txt
>a.txt
で、新規に1
へa.txt
を設定
+--./a.txt
+-----+ |
| 1|--+ (/dev/stdout)
| |
| 2|-----/dev/stderr
+-----+
2>a.txt
で、新規に2
へa.txt
を設定(別々に新規のファイルディスクリプタを生成するため、お互いに同じファイルに上書き合ってしまう)
+--./a.txt
+-----+ |
| 1|--+ (/dev/stdout)
| |
| 2|--+ (/dev/stderr)
+-----+ |
+--./a.txt
参考
man bash
によれば、bashでは下記の構文が利用できるらしい。これは>word 2>&1
のsemantically equivalentだそうなので、bashの場合はこれを使うと間違いがなさそう。
cmd &>word
また、>
や>&
、&>
以外にも、>|
や<>
など様々リダイレクトの種類があるらしい。
気になる方は、あなたの知らない>|と<>の使い方などを参考に。