標準入力を開き直す。

標準入力を開き直したい。


というか、正確にいえば一度EOFに達した標準入力からもう一度読みたい。
それだけなら別にclearerrすればいいだけ。
なんだけど、やりたいのは「別の」標準入力から読みたいということ。

えっと...。
要はmoreとかlessみたいなんを作りたいと。
まずはパイプから表示したい文字列を読み出して、それからキーボードからコマンドを受ける。ような感じ。



とりあえず書いてみる。

std::string text1;
std::string text2;
char ch;
while( ( ch = getchar() ) != EOF ) {
	text1 += ch;
}

clearerr( stdin );

while( ( ch = getchar() ) != EOF ) {
	text2 += ch;
}
std::cout << "text1: " << text1 << std::endl;
std::cout << "text2: " << text2 << std::endl;
return 0;


とりあえず実行してみる。

$ ./test
hoge
^D		/* ←"^D"はCtrl-D */
fuga
^D
text1: hoge	/* ←改行が入っちゃってるがキニシナイ */

text2: fuga

$

てな感じで両方キーボード入力なら問題ない。


問題があるのはパイプと合わせたとき。

$ echo -n hoge | ./test
text1: hoge
text2:
$

と言う感じでそもそもキーインを待たずに終了。
clearしようがseekしようがどうにもならず。


希望としては

$ echo -n hoge | ./test
fuga		/* ←ここでキーインできてほしい。 */
^D
text1: hoge
text2: fuga	/* ←こうでてほしい。 */

$

って感じになってほしいのだが...。


というわけで、partty*1だとかFlexターミナルエミュレータだとか作っちゃったりしたり何かして、こういうのに詳しそうな某友人に助けてもらいつつも解決。
ポイントとしては"close"と"dup"
stdinを閉じて"/dev/tty"を開き直してdupして標準入力にしてしまえばいいらしい。

std::string text1;
std::string text2;
char ch;
while( ( ch = getchar() ) != EOF ) {
	text1 += ch;
}

/* 標準入力をとりあえず閉じる */
close( STDIN_FILENO );
/* "/dev/tty"をひらく。 */
int newfd = open( "/dev/tty", O_RDONLY );
/* dupする */
dup( newfd );

while( ( ch = getchar() ) != EOF ) {
	text2 += ch;
}
std::cout << "text1: " << text1 << std::endl;
std::cout << "text2: " << text2 << std::endl;
return 0;

dupは利用可能な「もっとも小さい」ファイルディスクリプタに複製をするらしい。
ということなので、直前でSTDIN_FILENO(= 0)を閉じればSTDIN_FILENOに複製してくれる。
で、STDIN_FILENOが書き換わったから、以後の標準入力は新たに開き直した"/dev/tty"から読まれると。


lessのソースコードdupgrepしてみたら、まぁ、こんな感じのコードになってたからおっけーでしょう。
なにせ天下のgnuですから(w


ttyだとかそういう周りは苦手というか、あまり知らないのでちょっちとまどったヨ。
素人考えではなんとなく明示的にファイルディスクリプタを指定してやった方がいいのかな、ということで

int newfd = open( "/dev/tty", O_RDONLY );
dup2( newfd, STDIN_FILENO );

dup2を使ってみても動いた。
動いたから多分いいのだろう。うん。
dup2は必要なら勝手に閉じてくれるらしいのでcloseは書かなくてもいい、と思われる。


そういやfreopenってのをみっけた。
見た感じ同じようなことをやってくれるものらしい。

freopen( "/dev/tty", "r", stdin );

で。


動いた。


うん。俺みたいな初心者は、多分こっち使った方がよさそうだ。

*1:ターミナルを共有するツール。ペアプロとかにどぞ。