1

I have read that Ctrl-J sends '\n', Ctrl-M sends '\r' and Ctrl-D sends EOF, but either this is incorrect or I'm simply not understanding what's happening. This simple program illustrates:

const size_t SIZE = 100;

int main()
{
    char str[SIZE];
    printf("Enter string: ");
    size_t len = 0;
    char ch;
    while ((ch = getchar()) != '\r' && ch != '\n' && ch != EOF && len < SIZE -1)
    {
        str[len++] = ch;
        printf("ch is %i\n",ch);
    }
    printf("ch is %i\n",ch);
    str[len] = '\0';
    printf("%s\n",str);
    
    return 0;
}

When I enter: hello(Ctrl-j) the output is exactly what I would expect:

ch is 104
ch is 101
ch is 108
ch is 108
ch is 111
ch is 10
hello

but when I enter: hello(Ctrl-m) the output is exactly the same - i.e., the last ch is still 10, rather than the 13 that I was expecting.

And when I enter: hello(Ctrl-d) the output stops at the beginning of the line following "ch is 111" (the 'o'):

ch is 101
ch is 108
ch is 108
ch is 111

and waits there until I press Ctrl-d a second time after which it finishes with:

ch is -1
hello

or, if instead of a second Ctrl-D, I press Ctrl-J or Enter. In this case it finishes with

[blank line]
ch is 10
hello

losing the -1 and printing the 10 for '\n' instead.

I've tried this on a desktop running Arch Linux as well as a notebook running Ubuntu -- same results with both.

So, is there actually a way to send a CR from a keyboard under Linux, and why does the EOF need to be sent twice?

Jackdaw
  • 1,087
  • 2
  • 6
  • 19
joelk
  • 13
  • 2

1 Answers1

3

So, is there actually a way to send a CR from a keyboard under Linux

You are sending a CR, but the tty layer will translate it to a LF by default, so the program cannot receive a CR. Use tcsetattr() to disable the ICRNL mode if you want to avoid this. (But don't forget to save the old settings first, and reset them on exit.)

Search also for raw/cooked/cbreak modes.

and why does the EOF need to be sent twice?

There is actually no such thing as an EOF result at OS level – instead, whenever the read() system call returns 0 bytes, then libc functions report it as an EOF.

So Ctrl+D actually just tells the OS to immediately finish the read() call (that is, without waiting for a newline), and it only becomes an EOF indicator if it causes that read() call to return no data at all. That only happens if you press it immediately after a newline or after a previous Ctrl+D.

You'll see this happening in other programs too. Run strace -e read cat or strace -e read ./myprogram to see it happening in more detail.

u1686_grawity
  • 426,297
  • 64
  • 894
  • 966
  • 1
    Thanks. This opens up an entire new adventure for me. I'll have to do some studying before I play with these settings. Can I assume that if I change a c_iflag setting in my program it will affect only the terminal that the program is running in? Or my entire desktop environment? Or the entire system? (I would hope that doing this without root privileges shouldn't affect the entire system.) – joelk Feb 01 '21 at 05:34
  • It's a terminal-specific setting. Programs routinely use tcsetattr when they want to switch their terminal out of the "cooked" line-by-line mode into "raw" character-by-character read mode (e.g. whenever you open Nano or Vim or any other full-screen app). Even bash/zsh will change these parameters when showing the interactive prompt, so that they could handle arrow keys etc. – u1686_grawity Feb 01 '21 at 09:55
  • Very interesting. I've never really thought about things at this layer before; guess it's about time I did. One more thing, this ("... it only becomes an EOF indicator if it causes that read() call to return no data at all") is now clear to me. I saw it when I ran strace -e read cat, and actually I also saw it when running my example program & pressing ctrl-d immediately. But about this part "... Ctrl+D actually just tells the OS to immediately finish the read() call" -- I'd like to understand how that's accomplished. Can you point me towards where in Linux source & docs I can find that? – joelk Feb 01 '21 at 12:50
  • It's in [drivers/tty/n_tty.c](https://elixir.bootlin.com/linux/latest/source/drivers/tty/n_tty.c#L1313), search for `c == EOF_CHAR`. Overall it's part of the "ICANON" line-editing mode. – u1686_grawity Feb 01 '21 at 13:02
  • As for docs, see `man 3 termios`. – u1686_grawity Feb 01 '21 at 16:15