12

Running a program expecting input from terminal I can ”close” stdin by Ctrl+D. Is there any way to reopen stdin after that?

Andreas
  • 5,086
  • 3
  • 16
  • 36
  • You could fopen("/dev/tty", "r") – William Pursell Jul 05 '18 at 15:30
  • 1
    It is *not* 'reopening stdin', but I believe does what you actually want. – William Pursell Jul 05 '18 at 15:31
  • 1
    I find a simple `clearerr(stdin)` usually suffices in this case. – Steve Summit Jul 05 '18 at 15:33
  • 1
    Or `freopen("/dev/tty", "r", stdin)` – William Pursell Jul 05 '18 at 15:34
  • Reading from STDIN_FILENO after gettting '0' due to Ctrl-D seems to work just fine. Try ` char buf[32]; for(;;) printf("read=%lld\n", (long long)read(0,buf,sizeof(buf)));`. The Ctrl-D-caused 0 doesn't appear to be sticky for a terminal. – Petr Skocik Jul 05 '18 at 16:19
  • @NominalAnimal Nitpicking the nitpick: POSIX doesn't care. POSIX requires `STDIN_FILENO` to be `0`. – Petr Skocik Jul 05 '18 at 16:51
  • 1
    @PSkocik: Edited nitpick: Make that `char buf[32]; for (;;) printf("read=%zd\n", read(0, buf, sizeof buf));` instead. (I'm just so used to writing `STDIN_FILENO` for example code, I didn't even notice I changed that part. My point was to use `%zd` for `ssize_t`.) – Nominal Animal Jul 05 '18 at 16:55
  • @NominalAnimal Fair point. (I'm working on forgetting all of the % formatters by moving my codebase away from stdio, so apparently, it's working.). – Petr Skocik Jul 05 '18 at 17:07
  • @PSkocik: I just didn't want any new programmers to think a cast to `long long` was necessary, that's all. And yes, `` I/O is definitely not the fastest, if you read and write gigabytes of data (like I often do in HPC). If reading text-formatted data (molecules, atomic simulations), `` conversions are the bottleneck even on spinning disks. Custom parsers can do it basically at storage I/O rates, even for decimal-notation floating-point data. But I do like POSIX I/O (getline() especially) for simple input (config files and such). – Nominal Animal Jul 05 '18 at 17:24

1 Answers1

21

In and on POSIXy systems in general, the standard input descriptor is not closed when you press Ctrl+D in the terminal; it just causes the pseudoterminal layer to become readable, with read() returning 0. This is how POSIXy systems indicate end of input.

It does not mean the file descriptor (or even the stream handle provided on top of it by the C library) gets closed. As Steve Summit mentioned in a comment, you only need to clear the end-of-input status of the stream using clearerr(), to be able to read further data; this tells the C library that you noticed the status change, but want to try further reading anyway.

A similar situation can occur when a process is writing to a file, and another reads it. When the reader gets to the end of the file, a read() returns 0, which the C library understands as end-of-input; it sets an internal flag, so that unless you call clearerr(), feof() will return true for that stream. Now, if the writer writes more data, and the reader does a clearerr(), the reader can read the newly written additional data.

This is perfectly normal, and expected behaviour.

In summary:

  • End of input is indicated by a read() operation returning 0, but the file descriptor status does not change, and can be used as normal.

  • Ctrl+D on a terminal causes only that to happen; the file descriptors open to the terminal are not affected in any other way, and it is up to the foreground process reading the terminal input to decide what it does. It is allowed to simply go on reading more data.

    Most programs do exit when that happens, but that is a convention, not a technical requirement at all.

  • The C library detects read() returning 0, and sets its internal "end of input seen" flag for that stream. That causes feof() to return true, fgets() to return NULL, fgetc() to return EOF, and so on, for that stream.

  • Calling clearerr() on the stream handle clears the flag, so that the next read attempt will actually try to read further data from the descriptor.

    This is described in the very first sentence in the Description section of the man 3 clearerr man page.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
Nominal Animal
  • 38,216
  • 5
  • 59
  • 86
  • I don't think an example program is necessary. If you think it would be useful for multiple readers, I can whip one or two up. – Nominal Animal Jul 05 '18 at 16:15
  • Another example is random-access. If you read past the EOF, then clearerr and seek inside, then you can start to read again. – Gem Taylor Jul 05 '18 at 16:31
  • @GemTaylor: If you do an `fseek(stream, bytes, SEEK_SET)` or `fseek(stream, -bytes, SEEK_CUR)` or `fseek(stream, -bytes, SEEK_END)`, seeking backwards in the file, it also clears the end-of-stream flag; no need to call `clearerr()` too. – Nominal Animal Jul 05 '18 at 16:45
  • Even better :-) Still it demonstrates the principle that EOF does not mean the istream/FILE*/handle is defunct. Does it clear for seek relative 0? :-) – Gem Taylor Jul 05 '18 at 17:11
  • @GemTaylor: Yes. [`fseek()`](http://man7.org/linux/man-pages/man3/fseek.3.html) clears the internal end-of-file flag, unless it fails. (You cannot seek pipes and sockets. If `stream` refers to a file that was not truncated in the mean time, then `fseek(stream, 0, SEEK_END)` and `fseek(stream, 0, SEEK_CUR)` both succeed, clearing the internal end-of-file flag.) – Nominal Animal Jul 05 '18 at 17:18
  • wow, this clears upp a lot. however… I was hoping for the reopening to be performed from the terminal. It would be used to sort of "toggle" interactive mode. Ctrl+D toggles from interactive to non-interactive, and in a similar way I was hoping to toggle back to interactive, e.g. by entering Ctrl+D again. The process would check for EOF to determine current state. Should I improve the question or ask another? – Andreas Jul 12 '18 at 11:16
  • ...maybe I could do something using background jobs instead; `Ctrl-Z`, `bg`, capture the SIGTTIN signal, do something, profit. Definitely a completely different question. – Andreas Jul 12 '18 at 11:42
  • @Andreas: A new question makes most sense. Have you ever used [`screen`](http://man7.org/linux/man-pages/man1/screen.1.html)? Rather than use a single process to manage the terminal, you could have a terminal filtering process connected to the terminal (continuously), and via pipes or a socket pair to the actual process. You could detect EOF or INTR (Ctrl+C) in the filtering process, and "pause" (cache, without forwarding) the data to the actual process. Both processes can run the same program (it just does a `fork()` early on). It all really depends on what you wish to achieve. – Nominal Animal Jul 12 '18 at 15:46
  • @NominalAnimal Nope, never used `screen` before. Reading about SIGTTIN and in particular how jobs work they seem much more appropriate for what I'm trying to accomplish; letting a possibly windowed process do background work, having stdin open in case of automated testing, easy configuration using a scripted launcher, or importing/pasting big chunks of content. All I have to do is check `errno` for error and while set do background frames. This should work well on any platform, too. I admit, this is partly experiments I'm doing, knowing there are other mainstream technologies available. – Andreas Jul 13 '18 at 14:57