プログラムの出力をリダイレクトする

こんにちは、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$ 

ちゃんと入れ替わっているようです。

この例では、この記事で説明しなかった書き方もしています。 >&22>stderr.txt の部分ですね。 どんな操作なのかは考えてみて下さい。

では。