What is the difference between PS1 and PROMPT_COMMAND?
Asked Answered
M

7

152

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.

Madlynmadman answered 17/6, 2010 at 1:27 Comment(0)
C
77

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.

Concordant answered 17/6, 2010 at 1:38 Comment(0)
M
84

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
Metaphase answered 17/6, 2010 at 1:45 Comment(11)
You could shorten one of your lines: if git branch &>/dev/null ; then\ . It redirects both stdout and stderr to /dev/null. tldp.org/LDP/abs/html/io-redirection.htmlHeres
There is no need to export PROMPT_COMMAND.Vena
I think ceving's comment is very much true for this answer too: Don't set PS1 in PROMPT_COMMAND! Set variables in PROMPT_COMMAND and use them in PS1Sidneysidoma
I do not see a reason, why changing PS1 online within PROMPT_COMMAND is disadvantageous. It is perfect useful code. In contrast to Bob's answer down, the PS1 variable was correctly constructed. This allows a much more sophisticated bash prompt depending on your actual situation.Gaberdine
@ChristianWolf construction of PS1 inside PROMPT_COMMAND serves no purpose. it is an example how not to do it. construct PS1 once in .bash_profile, just use single quotes instead of double quotes, so that variable substitutions will be evaluated during each prompt.Nationalist
@Nationalist if I try it as you suggest with PS1 in single quotes` in .bash_profile I get a prompt like this: @deimos:\[\033[0;35m\] \[\033[0;34m\]/home/xsasgov \[\033[0;36m\]\[\033[0;31m\] $ \[\033[0;37m\]. For some reason my colours don't work. How do I fix that?Metaphase
@Nationalist - don't worry I think I've fixed it why the colours didn't appear.Metaphase
@Nationalist No I cannot confirm this. Assume you want to adjust PS1 depending on your current working path. Then you have to change PS1 for each new line of your command prompt, where PROMT_COMMAND matches perfectly. One example: If you are in a git working folder, put another PS1 style that shows the current branch as well.Gaberdine
One reason to avoid setting PS1 in PROMPT_COMMAND is that you can never reset it on the fly if you do, because it will be constantly reset. Not incredibly common, but still confusing when it happens.Zendejas
i like this pattern but agree with the issues with setting PS1 from PROMPT_COMMAND - there's no reason we can't do something like PROMPT_COMMAND="PROMPT_COMMAND_OUT=$(script_or_whatever)"; PS1="stuff and ${PROMPT_COMMAND_OUT} and things \$"Sexagenary
@Nationalist OMG the double-quote vs single-quote was killing me. Thanks for the explicit call out.Prut
C
77

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.

Concordant answered 17/6, 2010 at 1:38 Comment(0)
I
57

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.

Insurgence answered 19/6, 2012 at 18:57 Comment(8)
Don't set PS1 in PROMPT_COMMAND! Set variables in PROMPT_COMMAND and use them in PS1. Otherwise you you will loose the ability to use the PS1 escape sequences like \u or \h. You have to reinvent them in PROMPT_COMMAND. That might be possible but it is not possible to work around the lose of \[ and \] which mark the beginning and end of non printable characters. This means you can not use colors without confusing the terminal about the length of the prompt. And this confuses readline when editing a command spawning two lines. In the end you have a big mess on the screen.Cismontane
@Cismontane True that! One may use PROMPT_COMMAND to change the format of your PS1 and get the best of both worldsMatchless
PROMPT_COMMAND is executed before printing PS1. I see no problems setting PS1 from inside PROMPT_COMMAND, because after PROMPT_COMMAND is finished, the shell will print PS1, which was modified from PROMPT_COMMAND (or in this case, inside prompt_command) ?Numerical
Warning: PROMPT_COMMAND generally should not be used to print characters directly to the prompt. Characters printed outside of PS1 are not counted by Bash, which will cause it to incorrectly place the cursor and clear characters. Either use PROMPT_COMMAND to set PS1 or look at embedding commands. (Arch Wiki Source)Delogu
i don't get it why everyone tries to do some tricks in PROMPT_COMMAND instead of just using command substitution in PS1 export PS1='$(~/bin/bash_prompt)' does same thing bug looks saneNationalist
also while @Cismontane is correct regarding confusion, he is wrong regarding losing escape sequences. just try PROMPT_COMMAND='PS1="\u \h \$"'. it does not mean you should do that, it would be pointless waste of resourcesNationalist
@Cismontane bash and readline support \001 and \002 instead of [ and ], so this is not a problem.Jenette
@Ivanq In fact, \[ is just a prompt-specific alias for \001, just like \u is a prompt-specific alias for $USER.Emotionality
M
16

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'
Mallett answered 20/7, 2014 at 4:7 Comment(4)
You can also set the title of the terminal from PS1 without needing PROMPT_COMMAND, as the sequence that set the title can be included in PS1 wrapped with \[ and \].Vena
@Vena All right. Then let's do something else, such as dynamically setting an environment variable.Mallett
@Mallett you can dynamically set environment variable in PS1, it just will be set in subshell, so you can't get its value back. but your example is trivial PS1='$(sync)user \u on host \h$ 'Nationalist
As you can run shell commands in PS1 using syntax $(...) the PROMPT_COMMAND is only needed for stuff like setting environment variables (which PS1 cannot do so that it reflects to commands you will be running) and stuff like PROMPT_COMMAND="history -a;$PROMPT_COMMAND"Lipp
G
3

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?

Goggler answered 7/5, 2019 at 7:57 Comment(0)
N
2

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.

Nationalist answered 10/9, 2018 at 17:8 Comment(0)
A
1

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!

Amitie answered 10/1, 2023 at 1:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.