If a command's output does not end with a \n
, the next prompt appears, awkwardly, immediately afterwards:
$ echo -n hai
hai$
I just noticed a colleague whose shell (zsh, for what it's worth) is configured to print a %
(with background and foreground colours inverted for emphasis) followed by a \n
in such cases:
$ echo -n hai
hai%
$
I'd like to do the same. I use Bash. Is this possible? If so, what would I add to my ~/.bashrc?
UPDATE
I've spent several hours gaining an understanding of how gniourf_gniourf's solution works. I'll share my findings here, in case they are of use to others.
ESC[6n
is the control sequence introducer for accessing the cursor position (http://en.wikipedia.org/wiki/ANSI_escape_code).
\e
is not a valid representation of ESC when using echo
on OS X (https://superuser.com/q/33914/176942). \033
can be used instead.
IFS is Bash's internal field separator (http://tldp.org/LDP/abs/html/internalvariables.html#IFSREF).
read -sdR
looks like shorthand for read -s -d -R
, but in fact the "R" is not a flag, it's the value of the -d
(delimiter) option. I decided to write read -s -d R
instead to avoid confusion.
The double-parentheses construct, (( ... ))
, permits arithmetic expansion and evaluation (http://tldp.org/LDP/abs/html/dblparens.html).
Here's the relevant snippet from my .bashrc:
set_prompt() {
# CSI 6n reports the cursor position as ESC[n;mR, where n is the row
# and m is the column. Issue this control sequence and silently read
# the resulting report until reaching the "R". By setting IFS to ";"
# in conjunction with read's -a flag, fields are placed in an array.
local curpos
echo -en '\033[6n'
IFS=';' read -s -d R -a curpos
curpos[0]="${curpos[0]:2}" # strip leading ESC[
(( curpos[1] > 1 )) && echo -e '\033[7m%\033[0m'
# set PS1...
}
export PROMPT_COMMAND=set_prompt
Note: The curpos[0]="${curpos[0]:2}"
line is unnecessary. I included it so this code could be used in a context where the row is also relevant.
To customize the ZSH prompt, we need to modify the prompt= variable inside the . zshrc file. We can populate the prompt variable with various placeholders, which will alter how the ZSH prompt appears.
To make any change to the default zsh prompt, you'll have to add relevant values for the prompt to appear differently than the default. It'll be blank if you're accessing it for the first time. You can add a new line with the text PROMPT='...' and include relevant values in the ellipses.
Short answer: use %(!. #. $)
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:
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:
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With