[Go]os.Stdinへの全件読み込みを複数回すると、2回目以降先頭に余計な改行が必ず入ってきてしまう件
発端:
だが、2回目以降の more で、なぜか先頭行にいきなり空行が入ってしまうという問題が…なぜ?
リダイレクトなしの状態で、os.Stdin から2回全件読み込むと2回目の先頭に余計な空行が入ってしまうのです。検証プログラムを書いてみました。
package main
import (
"bufio"
"os"
"strings"
)
func main() {
sc1 := bufio.NewScanner(os.Stdin)
buf1 := []string{}
for sc1.Scan() {
buf1 = append(buf1, sc1.Text())
}
sc2 := bufio.NewScanner(os.Stdin)
buf2 := []string{}
for sc2.Scan() {
buf2 = append(buf2, sc2.Text())
}
print("sc1=[", strings.Join(buf1, ";"), "]\n")
print("sc2=[", strings.Join(buf2, ";"), "]\n")
}
結果:(nyagos はコンソールモードを変える場合があるので、素の CMD.EXE 下でやった)
A
B
C
^Z
1
2
3
^Z
sc1=[A;B;C]
sc2=[;1;2;3]
C:\Users\hymko\foo>
sc2=[;1;2;3]
を見ると、最初に入力した行を示す 1
の前に 0 文字の要素、つまり空行が入っている。これはどういうことでショウ?
bufio のせいではなさそう
package main
import (
"io/ioutil"
"os"
)
func main() {
buf1, _ := ioutil.ReadAll(os.Stdin)
buf2, _ := ioutil.ReadAll(os.Stdin)
print("buf1=[", string(buf1), "]\n")
print("buf2=[", string(buf2), "]\n")
}
C:\Users\hymko\foo\bar>bar
A
B
C
^Z
1
2
3
^Z
buf1=[A
B
C
]
buf2=[
1
2
3
]
2016年くらいに mattn 先生が立てた issue。Ctrl-Z を EOF として認識されていない件。当然ながら解決されているが、本件の調査をするために見るべきソースを特定するとっかかりになりそう
(まぁ、当然だが)C言語(MinGW)ではそのような動作はない
#include <stdio.h>
#include <stdlib.h>
char *readstr()
{
int c;
char *buffer = NULL;
size_t size=0;
while( (c = fgetc(stdin) ) != EOF ){
buffer = realloc(buffer,size+1);
buffer[size] = c;
size++;
}
buffer = realloc(buffer,size+1);
buffer[size+1] = '\0';
return buffer;
}
int main()
{
char *buffer1 = readstr();
char *buffer2 = readstr();
printf("buffer1=[%s]\n",buffer1);
printf("buffer2=[%s]\n",buffer2);
free(buffer1);
free(buffer2);
}
$ a.exe
A
B
C
^Z
1
2
3
^Z
buffer1=[A
B
C
]
buffer2=[1
2
3
]
os.Stdin をオープンし直すという回避策を見つけたが、いいのかなぁ、こんなことして(リソースリークしそう)
なお、os.Stdin をクローズしてしまうと、再オープンもできなくなってしまう模様
package main
import (
"io/ioutil"
"os"
)
func main() {
buf1, _ := ioutil.ReadAll(os.Stdin)
os.Stdin = os.NewFile(os.Stdin.Fd(), os.Stdin.Name())
buf2, _ := ioutil.ReadAll(os.Stdin)
print("buf1=[", string(buf1), "]\n")
print("buf2=[", string(buf2), "]\n")
}
$ bar
A
B
C
^Z
1
2
3
^Z
buf1=[A
B
C
]
buf2=[1
2
3
]
だめだ!nyagos に限っていうと、旧os.Stdinを指すポインタがいっぱい残っているので、インスタンスの作り直しは効かない。C言語でいうところの freopen 相当のものがあればよかったんだが…(うーん、全部直せるか?)
*os.Stdin = *os.NewFile(os.Stdin.Fd(), os.Stdin.Name())
とすると大丈夫の時もあるが、結構な頻度で、GetConsoleMode() がエラー終了する。あまり、やらん方がよさそうだ。
more で使う os.Stdin の方を os.NewFile してみたが、それでも動作が不安定になる。os.NewFile で os.Stdin 自体をクローンするのはやらない方が無難のようだ。。。
具体的に言うと:
f := os.NewFile(os.Stdin.Fd(), os.Stdin.Name())
この f を内蔵 more の入力元にして、元の os.Stdin は何も触らないようにする。
これで、Ctrl-Z の後の読み残しを f の方に置き去りにしようということを考えたわけだが、どうも、これを何回も行うとプロセス中の何らかの資源が枯渇してしまうようで、GetConsoleHandle や、子プロセスの git show が失敗したりと奇妙なエラーが発生するようになる。
ダメだこりゃ