3

From what I've read, the Linux terminal (in default settings) buffers the input and only sends it after receiving either EOF or '\n'.

When I loop c = getchar(); and check each c for being EOF (end then break) I need to do CTRL-D twice in order to stop reading, as the first EOF is always consumed by the terminal (I know one can change the terminal to raw, but maybe this is a bit overkill).

However, when I check for c being '\n' (which also sends the input) it will not be consumed.

Example:

  • Input: "abc\n"
    • Characters read: 'a','b','c','\n'
  • Input: "abc" and Ctrl-D
    • Characters read: 'a','b','c'
  • Input: "abc", Ctrl-D and Ctrl-D again
    • Characters read: 'a','b','c',EOF
  • Input: "abc", Return and Ctrl-D
    • Characters read: 'a','b','c','\n',EOF

Isn't this highly inconsistent? Or is there any rationale behind it?

I want to parse input including whitespaces and thus cannot check for '\n' but for EOF - is that possible without changing the terminal to raw?

I've tried with feof(stdin) too, but apperently this doesn't work either :/

ljrk
  • 751
  • 1
  • 5
  • 21

2 Answers2

4

You are misunderstanding what Ctrl-D does. Ctrl-D does NOT send an EOF -- it sends the current contents of the terminal buffer to the file descriptor so it will be returned by the next read call.

An EOF on the other hand, is when a read call returns 0 characters. So a ctrl-D ONLY results in an EOF when the terminal buffer is empty.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Ah, so basically every answer to 'how to send EOF to terminal' in the internet is wrong! xD But my program definitely confirms what you've said, so thanks for this! Is it possible to "send EOF" directly (so I could tell the user: Press X to terminate input) ? – ljrk Oct 24 '15 at 18:02
  • I've marked alexis' answer just because he was 30secs faster as what you've written is the same - shame only one can get the +rep :/ --- I will probably have to define one character that's not allowed then (or change terminal-type to raw) – ljrk Oct 24 '15 at 18:11
2

That's not how it works. ^D sends on the currently buffered line, so if you don't hit it immediately after a newline, your program will not see an empty read and will not know the input is ended. So you need another ^D to send nothing. But your program never sees a ^D.

In fact you can use stty to change the terminal's EOF character, so that for example ^N will end the input; your program will run exactly the same, because it does not depend of the value of the EOF keyboard character.

PS. Since you were trying to write a C program: the input calls return EOF when they try to read a file, and get an empty buffer (0 characters). In fact, EOF is not ^D as you imagine, but -1: This value was chosen because it does not fit in a byte, while read() could legally return any possible byte (yes, even '\04' aka ^D). This is why the signature of getchar() says it returns an int.

alexis
  • 48,685
  • 16
  • 101
  • 161
  • What do you mean with 'use ssty' ? If one would change `EOF` to eg. `^T`, wouldn't the user still need to press `^T` and then send the input via `^D` or Return afterwards? Thanks for the explanation of `^D` ! – ljrk Oct 24 '15 at 18:05
  • Try it! If you say `stty eof ^N`, you can use it to end input to your program (or to `cat` without arguments) without `^D` or return. It will simply take over the function of `^D` (which will no longer end input!). Note that I changed the example from `^T` to `^N`, because `^T` is bound to something else. – alexis Oct 24 '15 at 18:20
  • So basically it will send EOF 'and' send input? This'd be great, I need to read up on this – ljrk Oct 24 '15 at 18:26
  • I'm not sure what you're saying, but I don't think that's right: ^D tells the terminal you want to end the input, and the terminal ends it (or just flushes it) for you. But when a program reads there is no value that says "input is ended". The input just runs out, i.e. read returns nothing. See my edited answer for what happens inside a C program. – alexis Oct 24 '15 at 18:30
  • Hm, well, what I want to achieve is that the user can input any legal char but with some key-combination can end the input (forexample set stdin with 'eof' flag so that feof(stdin) returns true – ljrk Oct 24 '15 at 18:32
  • Also using `stty` seems not to work as it then also just sends the input. I'd need something like 'send everything in buffer and tell program to stop reading' – ljrk Oct 24 '15 at 18:34
  • 1
    Well, so we're back where you started: The user must type the tty's current eof character (^D or whatever you reset it to), at a time when there's no pending input to flush: either at the start of the line or immediately after another ^D/flush. Methinks this is a "problem" you simply don't need to "solve": Just write your program. – alexis Oct 24 '15 at 18:34
  • Well, `^D^D` is your friend then. :-) Just tell people to type that. – alexis Oct 24 '15 at 18:35
  • That's what I thought too, but then they might get confused that they sometimes only need to type Ctrl-D once (after return or no input), especially if my program is used in a script and they want to pipe 'Ctrl-D' into it. Then they maybe would also send Ctrl-D to another input – ljrk Oct 24 '15 at 18:36
  • 1
    This is how the unix terminal's "line discipline" works. Even if you don't like it, I wouldn't advise you to go implementing your own terminal drivers. If you don't want to trust your users to trigger an EOF, you can always use keywords to end input. E.g., "bye" or "exit" on a line by itself. That's how all modern interactive commandline editors work. (Often in addition to EOF) – alexis Oct 24 '15 at 18:41
  • 1
    Finally, note that if you switch to unbuffered input, you _will_ see the `^D` (so no more EOF). – alexis Oct 24 '15 at 18:42
  • Yep, kinda 'sad'. Probably I'm overdoing it anyway although the 'problem' was said to accept 'any' character, I doubt they really want me to do unbuffered input as this wouldn't fit into the difficulty of the program. They probably want me to end it via some sequence as you said. Thanks for the thorough explanations! – ljrk Oct 24 '15 at 18:43
  • Just re-read through the comments, wanted to hint you that you probably want to replace eof with eot here: "tty's current eof character (^D" ;-) – ljrk Oct 24 '15 at 19:23
  • It's EOT in the ASCII character table, but it's "eof" in the C API, in the Unix terminal protocol, and in the _stty_ manpage and commandline options. I can see it's confusing, but you'll just have to live with it. :-) – alexis Oct 24 '15 at 19:27
  • Oh, that's *really* confusing. Thanks for the explanation! – ljrk Oct 24 '15 at 19:29