Unix / Linux EOL is LF, linefeed, ASCII 10, escape sequence \n.Here's a Python snippet to get exactly one keypress:import sys, tty, termiosfd = sys.stdin.fileno()old_settings = termios.tcgetattr(fd)try:    tty.setraw(sys.stdin.fileno())    ch = sys.stdin.read(1)finally:    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)    return chWhen I press Enter on my keyboard in response to this snippet, it gives \r, carriage return, ASCII 13.On Windows, Enter sends CR LF == 13 10. *nix is not Windows; why does Enter give 13 rather than 10?It's known the Python snippet is correct.
Why doesn't the Enter key send EOL?
command line;terminal;newlines;ascii
While Thomas Dickey's answer is quite correct, Stphane Chazelas correctly mentioned in a comment to Dickey's answer that the conversion is not set in stone; it is part of the line discipline.In fact, the translation is completely programmable.The man 3 termios man page contains basically all the pertinent information. (The link takes to Linux man-pages project, which does mention which features are Linux-only, and which are common to POSIX or other systems; always check the Conforming to section on each page there.)The iflag terminal attributes (old_settings[0] in the code shown in the question in Python) has three relevant flags on all POSIXy systems:INLCR: If set, translate NL to CR on inputICRNL: If set (and IGNCR is not set), translate CR to NL on inputIGNCR: Ignore CR on inputSimilarly, there are related output settings (old_settings[1]), too:OPOST: Enable output processing.OCRNL: Map CR to NL on output.ONLCR: Map NL to CR on output. (XSI; not available in all POSIX or Single-Unix-Specification systems.)ONOCR: Skip (do not output) CR in the first column.ONLRET: Skip (do not output) CR.For example, you could avoid relying on the tty module. The makeraw operation just clears a set of flags (and sets the CS8 oflag):import sysimport termiosfd = sys.stdin.fileno()old_settings = termios.tcgetattr(fd)ch = Nonetry:    new_settings = termios.tcgetattr(fd)    new_settings[0] = new_settings[0] & ~termios.IGNBRK    new_settings[0] = new_settings[0] & ~termios.BRKINT    new_settings[0] = new_settings[0] & ~termios.PARMRK    new_settings[0] = new_settings[0] & ~termios.ISTRIP    new_settings[0] = new_settings[0] & ~termios.INLCR    new_settings[0] = new_settings[0] & ~termios.IGNCR    new_settings[0] = new_settings[0] & ~termios.ICRNL    new_settings[0] = new_settings[0] & ~termios.IXON    new_settings[1] = new_settings[1] & ~termios.OPOST    new_settings[2] = new_settings[2] & ~termios.CSIZE    new_settings[2] = new_settings[2] | termios.CS8    new_settings[2] = new_settings[2] & ~termios.PARENB    new_settings[3] = new_settings[3] & ~termios.ECHO    new_settings[3] = new_settings[3] & ~termios.ECHONL    new_settings[3] = new_settings[3] & ~termios.ICANON    new_settings[3] = new_settings[3] & ~termios.ISIG    new_settings[3] = new_settings[3] & ~termios.IEXTEN    termios.tcsetattr(fd, termios.TCSANOW, new_settings)finally:    termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)return chalthough for compatibility's sake, you might wish to check if all those constants exist in the termios module first (if you run on non-POSIX systems). You can also use new_settings[6][termios.VMIN] and new_settings[6][termios.VTIME] to set whether a read will block if there is no pending data, and how long (in integer number of deciseconds). (Typically VMIN is set to 0, and VTIME to 0 if reads should return immediately, or to a positive number (tenth of seconds) how long the read should wait at most.) As you can see, the above (and makeraw in general) disables all translation on input, which explains the behaviour cat is seeing:    new_settings[0] = new_settings[0] & ~termios.INLCR    new_settings[0] = new_settings[0] & ~termios.ICRNL    new_settings[0] = new_settings[0] & ~termios.IGNCRTo get normal behaviour, just omit the lines clearing those three lines, and the input translation is unchanged even when raw.The new_settings[1] = new_settings[1] & ~termios.OPOST line disables all output processing, regardless what the other output flags say. You can just omit it to keep output processing intact. This keeps output normal even in raw mode. (It does not affect whether input is automatically echoed or not; that is controlled by the ECHO cflag in new_settings[3].)Finally, when new attributes are set, the call will succeed if any of the new settings were set. If the settings are sensitive -- for example, if you are asking for a password on the command line --, you should get the new settings, and verify the important flags are correctly set/unset, to be sure.If you want to see your current terminal settings, runstty -aThe input flags are usually on the fourth line, and the output flags on the fifth line, with a - preceding the flag name if the flag is unset. For example, the output could bespeed 38400 baud; rows 58; columns 205; line = 0;intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = M-^?; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts-ignbrk brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel iutf8opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echokeOn pseudoterminals, and USB TTY devices, the baud rate is irrelevant.If you write Bash scripts that wish to read e.g. passwords, consider the following idiom:#!/bin/bashtrap 'stty sane ; stty '$(stty -g) EXITstty -echo -echonl -imaxbel -isig -icanon min 1 time 0The EXIT trap is executed whenever the shell exits. The stty -g reads the current settings of the terminal at the start of the script, so the current settings are restored when the script exits, automatically. You can even interrupt the script with Ctrl+C, and it'll do the right thing. (In some corner cases with signals, I've found that the terminal sometimes gets stuck with the raw/noncanonical settings (requiring one to type reset + Enter blindly at the terminal), but running stty sane before restoring the actual original settings has cured that every time for me. So that's why it's there; a sort of added safety.)You can read input lines (unechoed to the terminal) using read bash built-in, or even read the input character-by-character usingIFS=$'\0'input=while read -N 1 c ; do    [[ $c ==  || $c == $'\n' || $c == $'\r' ]] && break    input=$input$cdoneIf you don't set IFS to ASCII NUL, read built-in will consume the separators, so that c will be empty. Trap for young players.