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.

Upvotes: 4

Views: 793

Answers (2)

John Zwinck
John Zwinck

Reputation: 249364

A quick search of the GHC source code shows that clearerr() is not used at all there. However, you can open /dev/stdin again, since it looks like you're using Linux or similar. Try this:

stdin2 <- openFile "/dev/stdin" ReadMode

You can also use hDuplicate. See here: Portably opening a handle to stdin many times in a single session

Upvotes: 0

Daniel Wagner
Daniel Wagner

Reputation: 153077

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.

Upvotes: 5

Related Questions