yunomuのブログ

趣味のこと

getLineでファイルを最後まで円満に読む

UNIXっぽいストリームっぽいコマンドが作りたい。
といった時に、入力を「最後まで」読むにはどうしたらいいのという話。

何も考えずにcatっぽいのを書くとこうなる。catじゃなくてechoだな。

import System.IO

main :: IO ()
main = do
    l <- getLine
    putStrLn l
    main

テストデータを用意する。

% cat test.dat
Hello1
Hello2
Hello4

食わせる。

% cat test.dat | ./echo
Hello1
Hello2
Hello4
echo: <stdin>: hGetLine: end of file

EOLに達した時にgetLineが例外を吐いて、誰も処理してないからエラーメッセージが出る。

getLineのマニュアルを読むと、"hGetLine stdin"と同じものですよと書いてあって、hGetLineはEOFErrorを吐くのでisEOFErrorで判別してねとご丁寧に書いてあるが、そもそも例外処理ってどう書くのとかは書いてないんだねぇ。探せばあるけど。
catchIOErrorで処理するらしいですけど、なぜか関数が見つからなかったので、deprecatedだけどcatchを使った。型は同じだしいいよね。
catchIOError, catch :: System.IO.Error

import System.IO
import System.IO.Error

main :: IO ()
main = catch loop (\e -> if isEOFError e
                           then return ()
                           else ioError e)

loop :: IO ()
loop = do
    l <- getLine
    putStrLn l
    loop

EOFErrorだったときは見なかったことにして終了、それ以外の例外だったらさらに上位に投げ返すだけです。で、mainより上に届くとエラーメッセージが出るらしい。

% cat test.dat | ./echo
Hello1
Hello2
Hello4

いいのかこれで。なんかもうちょっと、EOF判定がいい感じにできる方法あるんじゃないかな。