How to interpret the PS1 variable in BASH? - bash

I have a bash command prompt like this:
someone#some-host:~$
I checked the PS1:
echo $PS1
It shows this:
\[\e]0;\u#\h: \w\a\]${debian_chroot:+($debian_chroot)}\u#\h:\w\$
How to interpret it? Why is it so complex?
I thought "\u#\h:\w$" should be enough. And I tried to set PS1 to it. It works the same or at least looks so.

How to interpret it?
\[ ... \] is interpreted by Bash as not visible. It matters if you want for example Home key to jump to the beginning of the line but right after prompt. Bash has to know, how many characters to jump to place the cursor right after the prompt, so it has to know how many characters were printed when printing the prompt.
https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Controlling-the-Prompt
\e]0;\u#\h: \w\a sets the title of your terminal. https://en.wikipedia.org/wiki/ANSI_escape_code#OSC_(Operating_System_Command)_sequences
${debian_chroot:+($debian_chroot)} Expands to the string (<value of debian_chroot>) when variable debian_chroot is set, otherwise expands to nothing. Basically it adds ( ) to the value of debian_chroot, when it is set to display it nicely. https://www.gnu.org/savannah-checkouts/gnu/bash/manual/bash.html#Shell-Parameter-Expansion

Related

Bash prompt changes when using arrow keys sometimes

When I use my terminal (iTerm 2 Mac) with my PS1 set to "\[\e[38;5;117m\W \e[39;38;5;104m\$\e[39;0m\] " and I use the arrow keys to go through my bash history it sometimes changes my prompt from ~ $ to just the first character of it and whatever command I'm looking at. For example, going to rvim .bashrc from randomDir $ ls. This problem also persists in the default terminal app.
\W and \$ should not go inside the \[...\], since bash will know how much space each takes up on the terminal.
PS1="\[\e[38;5;117m\]\W \[\e[39;38;5;104m\]\$\[\e[39;0m\] "
Only the characters that make up the ANSI escape sequence (which only instruct the terminal to change colors, without displaying a single additional character) are enclosed in \[...\].
Putting them inside \[...\] tells bash to ignore their contribution to the length of the prompt, leading to incorrect redraws.

Break line in terminal PS1 fix

I have this code to color my terminal:
export PS1="\e[1;30m\][\e[\e[1;30m\]\e[1;33m\] \u#\H \[\e[1;32m\]\w\[\e[0m\] \e[1;30m\]]\n[\[ \e[1;31m\]\T\[\e[0m\]\e[1;30m\] ] > \e[37m\]"
But I have one problem, when text should be in the new line it overwrites the first line.
Example:
In order for bash to figure out how much screen space your prompt takes up (and therefore where the actual command line starts), you have to enclose the non-printing parts of the prompt in \[...\]. Mostly, that means escape sequences like \e[1;30m need to be written as \[\e[1;30m\]. You have some \['s and \]'s in your prompt, but they're in the wrong places, which is making bash very confused. Finding all the printing and non-printing parts of a prompt as complex as yours is not trivial, but I think this gets it right:
export PS1='\[\e[1;30m[\e[\e[1;30m\e[1;33m\] \u#\H \[\e[1;32m\]\w\[\e[0m\] \[\e[1;30m\]]\n[ \[\e[1;31m\]\T\[\e[0m\e[1;30m\] ] > \[\e[37m\]'

What in PS1 is causing my Terminal.app commands to get stuck on the screen?

When cycling through statements entered to the console, I occasionally find that the text I entered isn't refreshed and the prompt is moved to the right.
My original, intended prompt: http://cl.ly/image/04080N260L1V.
What happens after hitting the Up and Down arrows about a dozen times: http://cl.ly/image/1n3S2K31340R.
In case the screenshots aren't clear, the underlined text (in this case, "vim ~/.bas") is getting "added" to the prompt. I can't delete it out. However, if I delete as much as I can, clearing any text after the prompt, and hit Enter, I'm greeted with my clean, original prompt again: http://cl.ly/image/2O1h1Z2y0n2I.
Here's what ~/.bash_profile contains:
# Simpler bash prompt in Terminal.app
promptColor="\e[1;34m"
endColor="\e[m"
#export PS1='\e[0;36m\w$ \e[0m'
export PS1="$promptColor\w$ $endColor"
# Syntax highlighting for commands like `ls` and such
alias ls="ls -G"
# PATH ammendment to put Homebrew-installed apps in front of system-provided ones
homebrew=/usr/local/bin:/usr/local/sbin:/usr/local/share/npm/bin
export PATH=$homebrew:$PATH
I've narrowed the culprit down to the PS1 variable. (You can see I've tried this a few different ways.) Based on what I've read, I'm using the color codes correctly.
Any help would be fantastic. Thanks.
This is a FAQ. In order for Bash to be able to compute the display length of the prompt correctly, any non-printing sequences such as color codes need to be inside a \[...\] sequence.
I think you want:
promptColor='\e[1;34m'
endColor='\e[m'
export PS1="$promptColor"'\w$ '"$endColor"
(Notice all the subtle changes from double to single-quotes)
The problem is that bash is doing expansion on the following when they need to be interpreted explicitly:
\e[1;34m
\w$
\e[m
Single-quotes and double-quotes mean different things in shell: Strong Quoting vs. Weak Quoting.
I would also just copy and paste the lines with escaped characters and modify them (note that they aren't the same as literal representations)

What is the difference between PS1 and PROMPT_COMMAND?

While taking a look at this awesome thread I noticed that some examples use
PS1="Blah Blah Blah"
and some use
PROMPT_COMMAND="Blah Blah Blah"
(and some use both) when setting the prompt in a Bash shell. What is the difference between the two? A Stack Overflow search and even a bit of broader Google searching aren't getting me results, so even a link to the right place to look for the answer would be appreciated.
PROMPT_COMMAND can contain ordinary Bash statements whereas the PS1 variable can also contain the special characters, such as '\h' for hostname, in the variable.
For example, here is my Bash prompt that uses both PROMPT_COMMAND and PS1. The Bash code in PROMPT_COMMAND works out what Git branch you might be in and displays that at the prompt, along with the exit status of the last run process, hostname and basename of the pwd.
The variable RET stores the return value of the last executed program. This is convenient to see if there was an error and the error code of the last program I ran in the terminal. Note the outer ' surrounding the entire PROMPT_COMMAND expression. It includes PS1 so that this variable is reevaluated each time the PROMPT_COMMAND variable is evaluated.
PROMPT_COMMAND='RET=$?;\
BRANCH="";\
ERRMSG="";\
if [[ $RET != 0 ]]; then\
ERRMSG=" $RET";\
fi;\
if git branch &>/dev/null; then\
BRANCH=$(git branch 2>/dev/null | grep \* | cut -d " " -f 2);\
fi;
PS1="$GREEN\u#\h $BLUE\W $CYAN$BRANCH$RED$ERRMSG \$ $LIGHT_GRAY";'
Example output looks like this in a non-Git directory:
sashan#dhcp-au-122 Documents $ false
sashan#dhcp-au-122 Documents 1 $
And in a Git directory you see the branch name:
sashan#dhcp-au-122 rework mybranch $
Update
After reading the comments and Bob's answer, I think that writing it as he describes is better. It's more maintainable than what I originally wrote above, where the PS1 variable is set inside the PROMPT_COMMAND, which itself is a super complicated string that is evaluated at runtime by Bash.
It works, but it's more complicated than it needs to be. To be fair, I wrote that PROMPT_COMMAND for myself about 10 years ago and it worked and didn't think too much about it.
For those curious as to how I've amended my things, I've basically put the code for the PROMPT_COMMAND in a separate file (as Bob described) and then echo the string that I intend to be PS1:
GREEN="\[\033[0;32m\]"
CYAN="\[\033[0;36m\]"
RED="\[\033[0;31m\]"
PURPLE="\[\033[0;35m\]"
BROWN="\[\033[0;33m\]"
LIGHT_GRAY="\[\033[0;37m\]"
LIGHT_BLUE="\[\033[1;34m\]"
LIGHT_GREEN="\[\033[1;32m\]"
LIGHT_CYAN="\[\033[1;36m\]"
LIGHT_RED="\[\033[1;31m\]"
LIGHT_PURPLE="\[\033[1;35m\]"
YELLOW="\[\033[1;33m\]"
WHITE="\[\033[1;37m\]"
RESTORE="\[\033[0m\]" #0m restores to the terminal's default colour
if [ -z $SCHROOT_CHROOT_NAME ]; then
SCHROOT_CHROOT_NAME=" "
fi
BRANCH=""
ERRMSG=""
RET=$1
if [[ $RET != 0 ]]; then
ERRMSG=" $RET"
fi
if which git &>/dev/null; then
BRANCH=$(git branch 2>/dev/null | grep \* | cut -d " " -f 2)
else
BRANCH="(git not installed)"
fi
echo "${GREEN}\u#\h${SCHROOT_CHROOT_NAME}${BLUE}\w \
${CYAN}${BRANCH}${RED}${ERRMSG} \$ $RESTORE"
And in my .bashrc file:
function prompt_command {
RET=$?
export PS1=$(~/.bash_prompt_command $RET)
}
PROMPT_DIRTRIM=3
export PROMPT_COMMAND=prompt_command
From the GNU Bash documentation page (Bash Reference Manual):
PROMPT_COMMAND
If set, the value is interpreted as a command to execute before
the printing of each primary prompt ($PS1).
I never used it, but I could have used this back when I only had sh.
The difference is that PS1 is the actual prompt string used, and PROMPT_COMMAND is a command that is executed just before the prompt. If you want the simplest, most flexible way of building a prompt, try this:
Put this in your .bashrc file:
function prompt_command {
export PS1=$(~/bin/bash_prompt)
}
export PROMPT_COMMAND=prompt_command
Then write a script (Bash, Perl, or Ruby: your choice), and place it in file ~/bin/bash_prompt.
The script can use any information it likes to construct a prompt. This is much simpler, IMO, because you don't have to learn the somewhat baroque substitution language that was developed just for the PS1 variable.
You might think that you could do the same by simply setting PROMPT_COMMAND directly to ~/bin/bash_prompt, and setting PS1 to the empty string.
This at first appears to work, but you soon discover that the readline code expects PS1 to be set to the actual prompt, and when you scroll backwards in history, things get messed up as a result.
This workaround causes PS1 to always reflect the latest prompt (since the function sets the actual PS1 variable used by the invoking instance of the shell), and this makes readline and command history work fine.
From man bash:
PROMPT_COMMAND
If set, the value is executed as a command prior to issuing each primary prompt.
PS1
The value of this parameter is expanded (see PROMPTING below) and used as the primary prompt string. The default value is ''\s-\v\$ ''.
If you simply want to set the prompt string, using PS1 alone is enough:
PS1='user \u on host \h$ '
If you want to do something else just before printing the prompt, use PROMPT_COMMAND. For example, if you want to sync cached writes to disk, you can write:
PROMPT_COMMAND='sync'
Yeah, so to try to really nail this down:
PROMPT_COMMAND is a handy Bash convenience variable/function, but there is, strictly speaking, nothing that cannot also be done using PS1 alone, correct?
I mean, if one wants to set another variable with scope outside the prompt: depending on the shell, that variable would probably need to be declared first outside $PS1 or (worst case) one might have to get fancy with something waiting on a FIFO prior to calling $PS1 (and armed again at the end of $PS1); the \u \h might cause some trouble, particularly if you're using some fancy regex; but otherwise: one can accomplish anything PROMPT_COMMAND can by using command substitution within $PS1 (and, maybe in corner cases, explicit subshells)?
Right?
The difference is that
if you output an incomplete line from PROMPT_COMMAND, it will screw your Bash prompt
PS1 substitutes \H and friends
PROMPT_COMMAND runs its contents, and PS1 uses its contents as the prompt.
PS1 does variable expansion and command substitution at each prompt. There isn't any need to use PROMPT_COMMAND to assign a value to PS1 or to run arbitrary code. You can easily do export PS1='$(uuidgen) $RANDOM' once in file .bash_profile. Just use single quotes.
I spent so much time on this I just wanted to share what worked for me. I looked at a lot of the SO posts about PROMPT_COMMAND and PS1 and tried many combinations of single quotes, double quotes, calling functions... I could not get the prompt to update each time without printing control characters or the literal expanded but not processed prompt string, or without just setting PS1 in PROMPT_COMMAND as we are advised not to do. My problem was setting variables (colors) that contained control characters; these had to be hard-coded after the variable name in PS1. PROMPT_COMMAND is set to a function that sets variables and they are used (escaped) in a double-quoted PS1 string. This is for a powerline-style prompt that changes colors with each command.
icon1=#unicode powerline char like
#these: https://github.com/ryanoasis/powerline-extra-symbols#glyphs
icon2=#same
#array of ANSI colors. 2 for rgb mode then the rgb values
#then 'm' without '\]' control character. these are from
#the solarized theme https://ethanschoonover.com/solarized/
declare -a colors=(
"2;220;50;47m"
"2;203;75;22m"
"2;181;137;0m"
"2;133;153;0m"
"2;42;161;152m"
"2;38;139;210m"
"2;108;113;196m"
"2;211;54;130m"
"2;0;43;54m"
"2;7;54;66m"
"2;88;110;117m"
"2;101;123;131m"
"2;131;148;150m"
"2;147;161;161m"
)
#outside of vars set in PROMPT_COMMAND it's ok to have control chars
LEN=${#colors[#]}
BG="\[\e[48;"#set bg color
FG="\[\e[38;"#set fg color
TRANSP="1m\]"#transparency
BASE2="2;238;232;213m\]"#fg (text) color
myfunc(){
RAND=$(($RANDOM % $LEN))
COLOR1=${colors[$RAND]}
COLOR2=${colors[($RAND + 1) % $LEN]}
COLOR3=${colors[($RAND + 2) % $LEN]}
}
PROMPT_COMMAND=myfunc
#note double quotes and escaped COLOR vars followed by hard-coded '\]' control chars
PS1="$BG$TRANSP$FG\$COLOR1\]$icon1$BG\$COLOR1\]$FG$TRANSP$BG\$COLOR1\]$FG$BASE2
[username hard-coded in unicode] $BG\$COLOR2\]$FG\$COLOR1\]$icon2$BG\$COLOR2\]$FG$BASE2
\w $BG\$COLOR3\]$FG\$COLOR2\]$icon2$BG\$COLOR3\]$FG$BASE2 [more unicode]
\[\e[0m\]$FG\$COLOR3\]$icon2\[\e[0m\] "
That ought to get you going!

Why is this bash prompt acting strangely/disappearing, and how do I fix it (OS X)?

I admit that I use a somewhat long-winded bash prompt:
--(username)-(Wed April 01|12:00:00)--(~ $
Recently, I got the bright idea to change it so that depending on the exit value from the previous command, if success, the interior elements of the ()'s would be green, and if failure, they would be red. I got it working for the most part (some odd exit statuses will change the color to something else, but I'm ok with it), but when typing a command which is more than one line, and causes the terminal to scroll, the prompt disappears! My prompt worked fine when there was no color, so I'm guessing it is related to my color escaping, and particularly my unclosed ['s, but I can't pin it down.
#.profile
export PS1='--(\e[$((32-${?}))m\u\e[0m)-(\e[$((32-${?}))m\d\e[0m|\e[$((32-${?}))m\T\e[0m)--(\e[$((32-${?}))m\w\e[0m \$ '
Thanks in advance!
It sounds like this should solve your problem.
This seems to work for me*:
export PS1='--(\[\e[$((32-${?}))m\]\u\[\e[0m\])-(\[\e[$((32-${?}))m\]\d\[\e[0m\]|\[\e[$((32-${?}))m\]\T\[\e[0m\])--(\[\e[$((32-${?}))m\]\w\[\e[0m\] \$ '
* well, really export PS1='\u#\h:\w\$ ' works for me
To quote the linked post, the answer lies in adding \[ and \] around all of your color sequences in your PS1 declaration:
Before I had the following value for PS1:
'\e[0;34m\h:\w [!]\$\e[0m '
which gave me a nice blue prompt of the following form
hostname:working-directory [command-number]$
However, I had the same line-wrapping problem you did. The fix was to insert \[ and \] around the ANSI escapes so that the shell knows not to include them in the line wrapping calculation. This results in the following value for PS1:
'\[\e[0;34m\]\h:\w [!]\$\[\e[m\] '
http://mywiki.wooledge.org/BashFAQ/053 -- I have a fancy prompt with colors, and now bash doesn't seem to know how wide my terminal is. Lines wrap around incorrectly.
By the way; for your reference; here's my PS1 which looks like this:
(source: lyndir.com)
\[$reset$bold$green\]\u#\h\[$blue\] \W \[$red\]${?/#0/\[$green\]}\$\[$reset\]
Notice how I put all the color codes in $parameters to make it neater, but more importantly, because you should be using tput to generate them. See:
http://mywiki.wooledge.org/BashFAQ/037 -- How can I print text in various colors?
I declare my color parameters in a utility script that gets sourced by my ~/.bashrc (and any scripts I write) which is called bashlib.
On a final note; put your PS1 definition in ~/.bashrc and don't export it. There's absolutely no reason why you should add your PS1 definition to the environment of any and all processes you spawn from your shell.
You just seem to be missing the start and end brackets around your escapes (before the first '\e' and after the last 'm'):
PS1='--(\[\e[$((32-${?}))m\u\e[0m)-(\e[$((32-${?}))m\d\e[0m|\e[$((32-${?}))m\T\e[0m)--(\e[$((32-${?}))m\w\e[0m\] \$ '
As mentioned, the PS1 var does not need to be exported: only your shell needs to see it.

Resources