43342


test.ml:

[code]

    let () = begin
      Scanf.scanf " %d %d " @@ Printf.printf "scanf : (%d, %d)\n";
      for i = 1 to 2 do
        print_string "read_line : ";
        print_endline @@ read_line ()
      done
    end
    
[/code]

input.txt:

[code]

    12 34
    hello world
    this is a test
    
[/code]

として、`cat input.txt | ocaml test.ml` とすると、

[code]

    scanf : (12, 34)
    read_line : Fatal error: exception End_of_file
    
[/code]

となり、`read_line`が例外を投げてしまいます(本当は

[code]

    scanf : (12, 34)
    read_line : hello world
    read_line : this is a test
    
[/code]

となって欲しいです)。

毎行`read_line`して`sscanf`を使えば期待する動作にできるのは分かるのですが、何故こうなるのかが分かりません。また、単に`scanf`と`read_line`を併用できるのであればその方法も教えて頂けると有難いです。


CC BY-SA 3.0
2018-04-18T02:13:25.910
43338
2018-04-19T04:43:32.050
28204
question
ocaml
OCamlでのscanf後のread_lineがEnd_of_fileを生じる理由


結論から先に言うと`Scanf.scanf`と他の標準入力を利用する関数の併用はうまくいきません。

`*scanf`関数は内部でバッファリングしています。これを行うのは
OCaml標準ライブラリソースコード[`stdlib/scanf.ml`](https://github.com/ocaml/ocaml/blob/4.06.1/stdlib/scanf.ml)
にある `from_ic`関数ですが、この関数は一度に最大1024バイトのデータをチャンネルから **一気に読み込めるなら読み込んでしまいます**
。このバッファは後続の同じ入力元に対する`*scanf`には使われますが、他の入力関数に回されることはありません。

この例のように、`cat input.txt | ocaml test.ml` のようにデータが一気にパイプやリダイレクションで渡される場合、
`from_ic`関数は`input.txt`ファイルの内容を一気に1024バイト分読みこもうとします。例では`input.txt`はとても短いので全内容が読み込まれてしまいます。`scanf
" %d %d
"`はファイル全内容の文字列に対して行われ、`12`と`34`を返しますが、`34`以降の文字列はこの`scanf`用バッファに残ったままです。この残りの文字列が次の`read_line
()`に使われることはありません。この時点で標準入力の全てが読み込まれているので...`read_line ()`は失敗します。

`input.txt`ファイルをもっと長く2000バイト位にしてこのプログラムに食わせると挙動がよく解ると思います。ファイルの途中から`read_line
()`の読み込みが始まります。

一方、ターミナルからの入力をこのプログラムに食わせる場合は質問者の意図通りの動きをします:

[code]

    $ ocaml z.ml
    12 34
    scanf : (12, 34)
    read_line : hello
    hello
    read_line : world
    world
    
[/code]

これはデータが改行入力毎に細切れに標準入力に入ってくるからです。`scanf`に消費される入力は始めの一行だけです。

というわけで、`Scanf.scanf`と標準入力関連関数は組み合わせてはいけない、という結論になります。`Scanf.scanf`を使う場合には`scanf`の呼び出しだけで標準入力の使用を完結しなければいけません(`scanf`を複数回呼び出すのは問題ない)。もしくは、質問にもあるように`read_line
()`したものに対して`Scanf.sscanf`を行うのが良い、ということになります。

いずれにせよ、`Scanf`は手軽そうに見えますけれども、ゴミがあっても読み捨ててエラーにならないなど、Cの`scanf()`と同じような問題があるので私は個人的には全く使いません。


CC BY-SA 3.0
2018-04-18T03:32:46.590
43342
2018-04-19T04:43:32.050
2018-04-19T04:43:32.050
2901
898
43338
answer