Configure shell to always print prompt on new line, like zsh Configure shell to always print prompt on new line, like zsh shell shell

Configure shell to always print prompt on new line, like zsh


A little trick using PROMPT_COMMAND:

The value of the variable PROMPT_COMMAND is examined just before Bash prints each primary prompt. If PROMPT_COMMAND is set and has a non-null value, then the value is executed just as if it had been typed on the command line.

Hence, if you put this in your .bashrc:

_my_prompt_command() {    local curpos    echo -en "\E[6n"    IFS=";" read -sdR -a curpos    ((curpos[1]!=1)) && echo -e '\E[1m\E[41m\E[33m%\E[0m'}PROMPT_COMMAND=_my_prompt_command

you'll be quite good. Feel free to use other fancy colors in the echo "%" part. You can even put the content of that in a variable so that you can modify it on the fly.

The trick: obtain the column of the cursor (with echo -en "\E[6n" followed by the read command) before printing the prompt and if it's not 1, print a % and a newline.

Pros:

  • pure bash (no external commands),
  • no subshells,
  • leaves your PS1 all nice and clean: if you want to change your PS1 sometimes (I do this when I work in deeply nested directory — I don't like having prompts that run on several miles), this will still work.

As tripleee comments, you could use stty instead of echoing a hard-coded control sequence. But that uses an external command and is not pure bash anymore. Adapt to your needs.


Regarding your problem with the ugly character codes that get randomly printed: this might be because there's still some stuff in the tty buffer. There might be several fixes:

  1. Turn off and then on the echo of the terminal, using stty.

    set_prompt() {    local curpos    stty -echo    echo -en '\033[6n'    IFS=';' read -d R -a curpos    stty echo    (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'}PROMPT_COMMAND=set_prompt

    the main difference is that the echo/read combo has been wrapped with stty -echo/stty echo that respectively disables and enables echoing on terminal (that's why the -s option to read is now useless). In this case you won't get the cursor position correctly and this might lead to strange error messages, or the % not being output at all.

  2. Explicitly clear the tty buffer:

    set_prompt() {    local curpos    while read -t 0; do :; done    echo -en '\033[6n'    IFS=';' read -s -d R -a curpos    (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'}PROMPT_COMMAND=set_prompt
  3. Just give up if the tty buffer can't be cleaned:

    set_prompt() {    local curpos    if ! read -t 0; then        echo -en '\033[6n'        IFS=';' read -s -d R -a curpos        (( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'    # else    #     here there was still stuff in the tty buffer, so I couldn't query the cursor position    fi}PROMPT_COMMAND=set_prompt

As a side note: instead of reading in an array curpos, you can directly obtain the position of the cursor in variables, say, curx and cury as so:

IFS='[;' read -d R _ curx cury

If you only need the y-position cury:

IFS='[;' read -d R _ _ cury


Thanks to Gilles on unix.stackexchange:

You can make the bash display its prompt on the next line if the previous command left the cursor somewhere other than the last margin. Put this in your .bashrc (variation by GetFree of a proposal by Dennis Williamson)

From the two linked answers I distilled this solution:

PS1='\[\e[7m%\e[m\]$(printf "%$((COLUMNS-1))s")\r$ '

Explanation:

  • \[\e[7m%\e[m\] -- reverse video percent sign
  • printf "%$((COLUMNS-1))s" -- COLUMNS-1 spaces. The COLUMNS variable stores the width of your terminal if the checkwinsize options is set. Since the printf is within a $() sub-shell, instead of printing to the screen its output will be added to PS1
  • \r a carriage return character

So, basically, it's a % sign, a long sequence of spaces, followed by a return key. This works, but to be honest I don't understand why this has the desired effect. Specifically, why does it look like it adds a line break only when it's needed, otherwise no extra line break? Why are the spaces necessary there?


if you do echo $PS1 you will see de current code of your prompt like this:
\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$

now prepend it with a \n like this:

PS1="\n\[\e]0;\u@\h: \w\a\]${debian_chroot:+($debian_chroot)}\u@\h:\w\$"

now your prompt will always begin on a new line.