読者です 読者をやめる 読者になる 読者になる

生涯未熟

プログラミングをちょこちょこと。

golangでScanが上手く動かなかったので原因調べてみた

どうも、今日も元気にGolang生活しております。

今回はbufioパッケージにあるScanの話をば。

問題の発生

とあるライブラリを使ってコードを書いていた時に、Scanを実行しても動かないという現象に遭遇しました。

コードは以下のような感じ

func main() {
    f, err := os.Open("hoge.txt")
    if err != nil {
        println("Err")
    }

    libraryFunc(f)

    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        println(scanner.Text())
    }
}

で、 hoge.txt の中身が出力されるのが予想される挙動だったんですが、出力されず・・・

どうしてだろうと、libraryFuncの中を読んでいくとこちらでもfを受け取ってScanを走らせていたことが分かり、「もしやScanって2回動かすとダメなのか?」と以下のようなコードを書いて調査してみました。

func main() {
    f, err := os.Open("hoge.txt")
    if err != nil {
        println("Err")
    }
    // 1回目
    err = file_scan(f)
    if err != nil {
        println("Err")
    }

    // 2回目
    err = file_scan(f)
    if err != nil {
        println("Err")
    }
}

func file_scan(file *os.File) error {
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    err := scanner.Err()
    return err
}

一旦、Scanを2回実行してみる形でコードを組んでみました。

で、これを実行してみてもやはり動かない。

原因は?

色々と調べてみてもあんまり理解を得なくて、結局Twitterで凄いエンジニアの方たちに調べてもらった結果、「ファイルのオフセットが回りきってる」という結論に。

というわけで、これまたコード書いて調べてみました。

func main() {
    f, err := os.Open("hoge.txt")
    if err != nil {
        println("Err")
    }
    ret, err := f.Seek(0, 1) //ファイルオフセットの現在位置を取得
    if err != nil {
       println("Err")
    }
    println(ret)
    // 1回目
    err = file_scan(f)
    if err != nil {
        println("Err")
    }
    ret, err := f.Seek(0, 1) //ファイルオフセットの現在位置を取得
    if err != nil {
       println("Err")
    }
    println(ret)
    // 2回目
    err = file_scan(f)
    if err != nil {
        println("Err")
    }
}

func file_scan(file *os.File) error {
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    err := scanner.Err()
    return err
}

実行してみると、「0 10」という結果が。

10行のファイルだったので、2回目実行前には既にファイルオフセットが最大の10になっていることが分かりますね。

対処法

対処としては、再度Scanを実行する前にファイルをオープンし直せば大丈夫でしょう。(何か他に良い方法を知っている方がおられましたらご教示下さい・・・)

[追記] もしくは2回目のScan実行前に f.Seek(0, 0) でファイルオフセットを0に戻してもいいかもしれません。

コードとしてはこんな感じ。

func main() {
    f, err := os.Open("hoge.txt")
    if err != nil {
        println("Err")
    }
    // 1回目
    err = file_scan(f)
    if err != nil {
        println("Err")
    }

    f, err = os.Open("hoge.txt")
    if err != nil {
        println("Err")
    }
    // 2回目
    err = file_scan(f)
    if err != nil {
        println("Err")
    }
}

func file_scan(file *os.File) error {
    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
    err := scanner.Err()
    return err
}

これで想定した挙動になりました!

思わぬところでハマりましたが、原因調べてハラオチできたので満足です。

教えてくださった方ありがとうございました!