6
3

More than 3 years have passed since last update.

改行コードって難しいっ

Last updated at Posted at 2020-04-13

はじめに

標準入力から文字列をつけとってなんやかんやする機会は多いと思います。Go で標準入力から文字列を受け取る方法の 1 つとして fmt.Scanf を使う方法があります。for ループを使った複数行の文字列を読み込んでいるときに Unix 系の OS と WindowsOS で挙動が異なることに気づきました。バグっぽい挙動を示しています。

問題になっているのは OS の差異による改行コードの違い、CRLF(\r\n) と LF(\n)によるものです。

公式リポジトリの Issue にもあがっていました。Open な Issue です。

サンプル

実際に fmt.Scanf を使う以下のような簡単な実装を考えてみます。

main.go
package main

import "fmt"

func main() {
    var f, s string

    fmt.Printf("Please enter first string:\n")
    _, err := fmt.Scanf("%s", &f)
    fmt.Println(err)

    fmt.Printf("Please enter second string:\n")
    _, err = fmt.Scanf("%s", &s)
    fmt.Println(err)
}

Unix 系で go run main.go として実行します。入力する文字列は hoge, fuga とします。

Unix系での実行

Please enter first string:
hoge
<nil>
Please enter second string:
fuga
<nil>

想定通りに読み込むことができました。

Windows系での実行

Windows 系で go run main.go として実行します。Unix 系のときと同様に入力する文字列は hoge, fuga とします。

Please enter first string:
hoge
<nil>
Please enter second string:
unexpected newline

見ると 2 回目の fmt.Scanf でエラーとして unexpected newline と出力されていることがわかります。これは想定外です。

詳細を見てみる

何が起きているか、詳細を確認します。デバッグ用の io.Reader を実装して確かめます。デバッグ用の io.Reader の実装は Issue からの引用です。

type myReader struct{}

func (r myReader) Read(p []byte) (n int, err error) {
    n, err = os.Stdin.Read(p)
    fmt.Fprintf(os.Stderr, "DEBUG: %q %v\n", p[:n], err)
    return n, err
}
func main() {
    input := myReader{}
    var f, s string

    fmt.Printf("Please enter first string:\n")
    _, err := fmt.Fscanf(input, "%s", &f)
    fmt.Println(err)

    fmt.Printf("Please enter second string:\n")
    _, err = fmt.Fscanf(input, "%s", &s)
    fmt.Println(err)
}
  • Unix系での実行
Please enter first string:
hoge
DEBUG: "h" <nil>
DEBUG: "o" <nil>
DEBUG: "g" <nil>
DEBUG: "e" <nil>
DEBUG: "\n" <nil>
<nil>
Please enter second string:
fuga
DEBUG: "f" <nil>
DEBUG: "u" <nil>
DEBUG: "g" <nil>
DEBUG: "a" <nil>
DEBUG: "\n" <nil>
<nil>

Process finished with exit code 0
  • Windows系での実行
Please enter first string:
hoge
DEBUG: "h" <nil>
DEBUG: "o" <nil>
DEBUG: "g" <nil>
DEBUG: "e" <nil>
DEBUG: "\r" <nil>
<nil>
Please enter second string:
DEBUG: "\n" <nil>
unexpected newline

上記の結果から、Windows 系では 2 回目の読み込みの際に \n を読み込もうとしていることがわかります。

で、これは Pob Pike 氏が以下のようにコメントしています。Oh...

The scanning code in fmt is cursed. cc @rsc

進行中のCL

レビュー中の CL は以下です。

暫定対応

暫定的には以下のように末尾に \n を付与する ことで WindowsOS でも想定通りエラーにならずに動作します。(Unix 系の OS でも同様に動きます)

func main() {
    var f, s string

    fmt.Printf("Please enter first string:\n")
-   _, err := fmt.Scanf("%s", &f)
+   _, err := fmt.Scanf("%s\n", &f)
    fmt.Println(err)

    fmt.Printf("Please enter second string:\n")
-   _, err = fmt.Scanf("%s", &s)
+   _, err = fmt.Scanf("%s\n", &s)
    fmt.Println(err)
}
Please enter first string:
hoge
<nil>
Please enter second string:
fuga
<nil>

参考: https://stackoverflow.com/questions/17401709/why-doesnt-fmt-scanf-in-go-wait-for-user-input/17403785

エラーにならずに文字列を読み込むことができました。

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3