Bash – How to always print the prompt on a new line & keep input

bashinputprompt

We all know this annoying issue:

$ printf "abc" > some-file
$ cat some-file
abc$ 

which tends to mess up the prompt if it is any more complex than $, throws off the current cursor position and looks just plain ugly. There are some solutions (e.g. here), but they have another drawback: Logging into a slow machine over ssh, you may start to type before the prompt appears. Normally, your input is then buffered and displayed nicely as soon as possible. However, with the linked solution, the input is discarded.

How do I

  • always start the prompt on a new line, esp. if the output of the last command did not end with a new line and
  • keep unconsumed input already entered during the execution of the previous command in the command line buffer?

Best Answer

This solution slightly combines the solution from the question with a small perl snippet:

The following code in .bashrc defines two new functions, inject and clear_newline. The latter is then called every time a prompt has to be printed. The former is called from within the latter (see inline commands for the details):

# injects the arguments into the terminal, from the second link
function inject() {
  perl -e 'ioctl(STDIN, 0x5412, $_) for split "", join " ", @ARGV' "$@"
}
# prints a newline if the cursor is not in position 1
clear_newline() {
  local curpos # local variable to hold the cursor position
  local input  # local variable to hold entered input
  stty -echo # disable echoing
inject '
' # inject a newline to terminate entered input, '\0' didn't work?
  IFS='\n' read -s input # read entered input
  echo -en '\033[6n'
  # ask the terminal driver to print the current cursor position
  IFS=';' read -d R -a curpos # read cursor position 
  stty echo # enable echoing
  (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
  # if cursor not in first column, print a % with inverted colours and a newline
  stty -echo # disable echoing of input to terminal again
  inject "${input}" # inject stored input
  stty echo # enable echo
}

PROMPT_COMMAND='clear_newline' # run clear_newline for every prompt

The second stty -echo/stty echo pair is necessary to hide the input injected; it will be printed by bash as soon as the prompt finishes.

Related Question