Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I read from standard input again after an EOF?

I have the following C program:

#include <stdio.h>
#include <unistd.h>

void readAndEchoAll(void) {
    for(;;) {
        char buf[100];
        ssize_t size = read(STDIN_FILENO, buf, sizeof(buf));
        if(size <= 0) {
            return;
        }
        fwrite(buf, 1, size, stdout);
    }
}

int main(void) {
    puts("Reading and echoing STDIN until first EOF...");
    readAndEchoAll();
    puts("Got first EOF. Now reading and echoing STDIN until second EOF...");
    readAndEchoAll();
    puts("Got second EOF.");
    return 0;
}

When I run it, it works the way I want it to. Here's what it does:

Reading and echoing STDIN until first EOF...
asdf
^Dasdf
Got first EOF. Now reading and echoing STDIN until second EOF...
fdsa
^Dfdsa
Got second EOF.

I'm trying to create an equivalent Haskell program. Here's my attempt:

readAndEchoAll :: IO ()
readAndEchoAll = do
    buf <- getContents
    putStr buf

main :: IO ()
main = do
    putStrLn "Reading and echoing STDIN until first EOF..."
    readAndEchoAll
    putStrLn "Got first EOF. Now reading and echoing STDIN until second EOF..."
    -- ???
    readAndEchoAll
    putStrLn "Got second EOF."

This doesn't work. Here's what it does:

Reading and echoing STDIN until first EOF...
asdf
^Dasdf
Got first EOF. Now reading and echoing STDIN until second EOF...
readtwice.hs: <stdin>: hGetContents: illegal operation (handle is closed)

How do I make this work like the C program? I assume that I need to put some equivalent of clearerr(stdin); where I have -- ???, but I'm not sure what that equivalent is.

Update: Turns out clearerr is a bit of a red herring, as it's exclusive to the standard C API. When using the POSIX API, you can just read again without needing to do anything equivalent to it. So rather than make Haskell do anything extra, I need to make it not do something: not prevent further reads once it sees EOF.

like image 803
Joseph Sible-Reinstate Monica Avatar asked May 20 '19 13:05

Joseph Sible-Reinstate Monica


1 Answers

You can't use getContents, because hGetContents (semi-)closes the handle it's passed and getContents calls hGetContents. But there's no problem with reading from a handle again after EOF with most of the other functions from the standard library. Here's a simple but inefficient example of reading all the characters without using getContents:

import Control.Exception
import System.IO.Error

readAll = go [] where
    handler cs err = if isEOFError err
        then return (reverse cs)
        else throwIO err
    go cs = catch (do
        c <- getChar
        go (c:cs))
        (handler cs)

main = do
    all <- readAll
    putStrLn $ "got: " ++ all
    putStrLn "go again, mate"
    all <- readAll
    putStrLn $ "got: " ++ all

If you want better efficiency, there are various functions available for reading lines-at-a-time or other large chunks in the standard library, rather than one character at a time.

like image 129
Daniel Wagner Avatar answered Oct 06 '22 07:10

Daniel Wagner