Open

[Go]os.Stdinへの全件読み込みを複数回すると、2回目以降先頭に余計な改行が必ず入ってきてしまう件

9

発端:

https://zenn.dev/zetamatta/scraps/93d903e1d1382f#comment-fd4f97a45bbe07

だが、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 下でやった)

\Users\hymko\foo>foo
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
]

(まぁ、当然だが)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 が失敗したりと奇妙なエラーが発生するようになる。

ダメだこりゃ

ログインするとコメントできます