Search code examples
bashcarriage-returnline-endings

Newlines from a background process aren't working while ssh is running


I have a process that prints some lines of text running in the background. If I start ssh, the newline characters don't work properly.

user@localhost:~$ { sleep 5; echo -e "1\n2\n3" ; } &
[1] 26215
user@localhost:~$ 1
2
3
user@localhost:~$ { sleep 5; echo -e "1\n2\n3" ; } &
user@localhost:~$ ssh localhost # quickly
user@localhost:~$ 1
                   2
                    3

I would expect 2 and 3 to start at the beginning of a newline regardless of whether ssh is running or not. Can someone explain what's happening here? Is there a way to fix things so newlines still work?

Oddly, if I throw some \r into the mix the newlines seem to work again, although I need to replace the default echo newline with a \r too.

user@localhost:~$ { sleep 5; echo -en "1\r\n2\r\n3\r\n" ; } &
[1] 7066
user@localhost:~$ 1
2
3

Solution

  • By default in an interactive shell the tty/pty is in canonical mode and \n written to the tty will be automatically converted to \r\n.

    When you ssh to a remote server to start an interactive shell, ssh will change the local tty to raw mode (or non-canonical mode) and in raw mode \n will not be converted to \r\n.

    See the following example (highlighted some part with ^ chars):

    $ tty
    /dev/pts/2
    $ stty -a -F /dev/pts/2
    speed 38400 baud; rows 43; columns 80; line = 0;
    intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>;
    eol2 = <undef>; swtch = <undef>; start = ^Q; stop = <undef>; susp = ^Z;
    rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
    -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
    -ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
    -iuclc ixany -imaxbel iutf8
    opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
    ^^^^^               ^^^^^
    isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
         ^^^^^^
    echoctl echoke -flusho -extproc
    $
    $ ssh -t 127.0.0.1 bash --norc
    bash-5.1# tty
    /dev/pts/13
    bash-5.1# stty -a -F /dev/pts/2
    speed 38400 baud; rows 43; columns 80; line = 0;
    intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>;
    eol2 = <undef>; swtch = <undef>; start = ^Q; stop = <undef>; susp = ^Z;
    rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
    -parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
    -ignbrk -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff
    -iuclc -ixany -imaxbel iutf8
    -opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
    ^^^^^^
    -isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt
          ^^^^^^^
    echoctl echoke -flusho -extproc
    bash-5.1#
    

    As you've found out, to force the \r\n behavior you can explicitly use \r\n:

    printf '1\r\n2\r\n3\r\n'
    

    You can see stty's manual for more details about the misc flags.