こんにちは、masm11 です。
今回は、プログラムの出力を操作する話を書きたいと思います。 「なんだそんなことか」と思われるかもしれませんが、 なかなか高度なこともできます。
ファイルディスクリプタとは
プログラムの出力を操作するには「ファイルディスクリプタ」というものについて知っておく必要があります。 リダイレクトの前にこれについて軽く勉強してみます。
C言語で
int fd = open("/tmp/foo", O_WRONLY);
などとファイルを開くと、その結果として 0 以上の番号が得られます。 これがファイルディスクリプタです。
ファイルに対して入出力する時には、このファイルディスクリプタを使って、
write(fd, "123", 3);
などとします。fd
は先程得られたファイルディスクリプタです。
fd
が示すファイル(つまり /tmp/foo
)に対して 123
という3バイトを書き込んでいます。
そして、ファイルディスクリプタ 0〜2 は特別な意味を持っていて、
- 0 は標準入力
- 1 は標準出力
- 2 は標準エラー出力
をそれぞれ示すことになっています。
dup2()
とは
open()
の他に、dup2()
というシステムコールがあります。
これは、元々のファイルディスクリプタとは別の、指定したファイルディスクリプタからでも使えるように する機能があります。
例えば
int fd = open("/tmp/foo", O_WRONLY);
とすると、fd
を使って /tmp/foo
に書き込むことができますが、dup2()
を使って
dup2(fd, 1);
とすると、fd
の代わりに 1
でも /tmp/foo
に書き込むことができるようになります。
リダイレクト
さて、リダイレクトです。
program > log.txt
よく目にする、普通のリダイレクトです。
シェルは、今までに説明した open()
と dup2()
を使って、
int fd = open("log.txt", O_WRONLY); dup2(fd, 1);
をした後に program
を実行します。すると、program
にとって標準出力のファイルディスクリプタ 1 は
log.txt
につながっているため、program
が普通に標準出力に出力するだけで、log.txt
への出力となります。
2>&1
とは
次に、
program > log.txt 2>&1
これもよく目にすると思います。
シェルは
// > log.txt の処理 int fd = open("log.txt", O_WRONLY); dup2(fd, 1); // 2>&1 の処理 dup2(1, 2);
をした後に program
を実行します。
dup2(1, 2);
をすることで、1 だけでなく 2 でも log.txt
に出力されるようになっているわけです。
なお、間違って
program 2>&1 > log.txt
と実行し、「あれ?」と思った方、いませんか?
// 2>&1 の処理 dup2(1, 2); // > log.txt の処理 int fd = open("log.txt", O_WRONLY); dup2(fd, 1);
dup2(1, 2);
の時、1 につながっているファイルに 2 からでも出力されるようになるわけですが、
通常では 1 には端末がつながっているため、2 も同じ端末へ出力されるようになります。
ただ、通常では(つまりリダイレクトしなければ) 2 は 1 と同じ端末に出力されるようになっていますので、
このタイミングでの 2>&1
は無意味となります。
そしてその後に > log.txt
が処理され、1 が log.txt
につながります。
そのため、標準エラー出力は引き続き端末のままとなるわけです。
順序には気をつけましょう。
標準出力と標準エラー出力を入れ替える
では最後に、標準出力と標準エラー出力を入れ替えてみます。
program 3>&2 2>&1 1>&3 3>&-
こうすることで入れ替えられます。順を追って見ていきます。
// 3>&2 dup2(2, 3);
こうすると、2 につながっているファイルに対して、3 からでも出力できるようになります。 3 というファイルディスクリプタは、標準入力でも標準出力でも標準エラー出力でもない、普通のファイルディスクリプタです。
// 2>&1 dup2(1, 2);
こうすると、1 につながっているファイルに対して、2 からでも出力できるようになります。
さて、その上で、
// 1>&3 dup2(3, 1);
です。3 につながっているファイルに対して、1 からでも出力できるようになります。 ここで 3 は、上に書いたとおり、元々 2 につながっていたファイルです。 ですので、1 からは元々 2 につながっていたファイルに出力されることになります。
以上で、program
を実行すると、標準出力は標準エラー出力へ、標準エラー出力は標準出力へ出力されるようになるわけです。
ただ、3 は、1 と 2 を入れ替えるために一時的に割り当てたもので、
program
には不要です。ですので、
// 3>&- close(3);
で 3 を閉じています。
実際に試してみましょう。
bash-3.2$ ( > ( > echo stdout > echo stderr >&2 > ) 3>&2 2>&1 1>&3 3>&- > ) > stdout.txt 2>stderr.txt bash-3.2$ cat stdout.txt stderr bash-3.2$ cat stderr.txt stdout bash-3.2$
ちゃんと入れ替わっているようです。
この例では、この記事で説明しなかった書き方もしています。
>&2
と 2>stderr.txt
の部分ですね。
どんな操作なのかは考えてみて下さい。
では。