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. IfPROMPT_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 yourPS1
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:
Turn off and then on the
echo
of the terminal, usingstty
.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 withstty -echo
/stty echo
that respectively disables and enables echoing on terminal (that's why the-s
option toread
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.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
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 read
ing 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 signprintf "%$((COLUMNS-1))s"
--COLUMNS-1
spaces. TheCOLUMNS
variable stores the width of your terminal if thecheckwinsize
options is set. Since theprintf
is within a$()
sub-shell, instead of printing to the screen its output will be added toPS1
\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.