How to type a command to execute when the current command completes

commandcommand lineterminal

I have a function (not) that displays a notification. Hence, I can use it to show when a command is complete.

sleep 5; not

However, sometimes I forget to add ;not. In OS X's terminal (a few years ago), I could enter a second command, while the current command was running. This second command would execute after the first had finished.

Thus, I'd type sleep 5Enter, then immediately type notEnter. After sleep had terminated, not would execute.

However, my experience in Linux is that this does not occur. After sleep terminates, the command line shows not, but the Enter never registers. I've tested Terminator, Konsole and a tty.

Is this behaviour dependent on the terminal emulator, and if so, are there any that work as I want? Alternatively, is there a way to make this function work in my terminal of choice (Terminator)?

Testing in different shells

Doesn't work, i.e. the second command doesn't register:

  • bash
  • bash --norc
  • bash --norc --noprofile
  • sh

Does work, i.e. the second command registers:

  • bash --norc --noprofile --noediting
  • zsh

I selectively removed lines from ~/.inputrc and tested with my default bash shell again. I traced the problem to the following line. When removed, the second command registers as expected.

Control-j: menu-complete

Oddly enough, if I try binding (say) Ctrl+i instead, there is no problem. Why does this entry prevent the second command from registering, and is there a way to still use Ctrl+j for menu-complete while having the behaviour that I want for this second command?

Best Answer

Well, according to some of your edits you've got CTRL+J bound to a bindkey macro command. That explains your bash issue considering readline's default behavior.

Generally readline reads input in something very like stty raw mode. Input chars are read in as soon as they are typed and the shell's line-editor handles its own buffering. readline sets the terminal to raw when it takes the foreground, and it restores it to whatever its state was beforehand when calling up another foreground process group.

CTRL+J is an ASCII newline. ABCDEFGHIJ is 10 bytes from NUL. Because you have configured readline to eat this character and subsequently to expand away what remains of any command-line on which it does with menu-completion, type-ahead won't work. The terminal is in a different state when the type-ahead is buffered by the kernel's line-discipline than it is when readline is in the foreground.

When readline is in the foreground it does its own translation for input carriage returns -> newlines and the terminal driver doesn't convert it at all. When you enter your type-ahead input, though, the terminal driver will typically translate returns to newlines as can be configured with stty [-]icrnl. And so your return key is sufficient for commands entered live, but the newlines sent by the terminal's line-discipline are being interpreted as menu-complete commands.

You might tell the terminal driver to stop this translation with stty -icrnl. This is likely to take at least a little bit of getting used to. Other commands that accept terminal input will usually expect newlines rather than returns, and so you'll either have to explicitly use CTRL+J when they control the foreground, or else teach them to handle the returns as bash does.

You've already mentioned that read doesn't work as expected when reading form the terminal. Again, it likely would if you explicitly used CTRL+J to end an input line. Or... you can teach it:

read()
    if    [ -t 0 ]
    then  command read -d $'\r' "$@"
    else  command read "$@"
    fi

It will probably be a lot less hassle in the long-run if you found a different key for menu-complete, though. Newlines are kind of a big deal for most terminal applications.

Related Question