8

My PS1 in my ~/.bash_profile:

export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]`__git_ps1`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

(sorry, I don't have any aliases for my color codes, I created this prompt with an online editor)

Which is a bit messy but produces a very nice prompt: enter image description here

But the current branch displayed is always wrong if I switch

wrong branch

I'm not sure why this would happen. If I run the command by itself, I get the correct value.

$ echo `__git_ps1`
(dev)

If I source the .bash_profile the new value will come in (but will be wrong next time I switch). Am I doing something wrong?

1
  • Use single quotes around the assignment (or just the __git_ps1 part so you don't need to fix escapes all through the prompt) so it isn't called/evaluated immediately and is left unexpanded for the shell to call at prompt display time. Nov 26, 2015 at 3:24

4 Answers 4

9
export PS1="…`__git_ps1`…"

With `__git_ps1` inside double quotes, this command runs the command __git_ps1 and assigns its output (and other surrounding text) to the variable PS1. Thus your prompt is showing the branch that was determined when your .bash_profile was executed.

You need to run __git_ps1 each time bash displays a prompt. (Actually you don't need to run it again until the git information has changed, but that's difficult to detect.) There are two ways to do that.

  • Include the literal text `__git_ps1` in the PS1 variable. Make sure that bash is configured to perform shell expansions on the prompt string, with the promptvars option turned on; that's the case by default but it can be turned off with shopt -u promptvars.

    PS1='\n\[…\]$(__git_ps1)\[…\]\$ '
    
  • Update the prompt content by a command run from the PROMPT_COMMAND variable.

    update_PS1 () {
      PS1="\\n\\[…\\]$(__git_ps1)\[…\]\\$ "
    }
    shopt -u promptvars
    PROMPT_COMMAND=update_PS1
    

By the way, the prompt is a shell configuration, not a global setting, so you should set it in ~/.bashrc, not in ~/.bash_profile.

1
  • 1
    First option worked like a charm, thanks! Aug 7, 2018 at 14:22
5

It's just a simple matter of quoting. Change `__git_ps1` to \$(__git_ps1), or, if you must use backticks: \`__git_ps1\`.

To convince yourself just change your PS1 to (open a new shell instance if you want to get back cleanly to your previous setup):

$ PS1="$(date) >"
Thu Nov 26 20:02:34 EST 2015 >_

The only problem is that it will not update (wait some seconds to press enter).

But this will:

$ PS1="\$(date) >"
Thu Nov 26 20:06:20 EST 2015
Thu Nov 26 20:06:25 EST 2015

That's all. write exit. (update your prompt) Get back to work.

1

http://mediadoneright.com/content/ultimate-git-ps1-bash-prompt

Notice down at the bottom they don't set:

export PS1="\n... `__git_ps1 ...

The set it to this sick ass piece of work:

export PS1=$IBlack$Time12h$Color_Off'$(git branch &>/dev/null;\

if [ $? -eq 0 ]; then \
  echo "$(echo `git status` | grep "nothing to commit" > /dev/null 2>&1; \
  if [ "$?" -eq "0" ]; then \
    # @4 - Clean repository - nothing to commit
    echo "'$Green'"$(__git_ps1 " (%s)"); \
  else \
    # @5 - Changes to working tree
    echo "'$IRed'"$(__git_ps1 " {%s}"); \
  fi) '$BYellow$PathShort$Color_Off'\$ "; \
else \
  # @2 - Prompt when not in GIT repo
  echo " '$Yellow$PathShort$Color_Off'\$ "; \
fi)'

The relevant bits of which are "$(__git_ps1 " (%s)" or "$(__git_ps1 " {%s}"

2
  • The relevant bits of which are actually the single quotes. Nov 26, 2015 at 3:23
  • And actually many things about that snippet are sub-par at best. Nov 26, 2015 at 3:27
0

I feel the existing answers don't accurately/clearly communicate the root cause. Although they may lead to arriving at a solution, I'm unsure they teach the viewer the lesson that should be learned. This answer is focused on answering from the angle of helping the viewer arrive at that understanding.

Quick answer

I thought of this method after writing up the original response, so leaving the detailed response below but here's the quick answer.

Your PS1 value is being stored as the substituted version because escaping was not used during input. You can validate it like this:

Original:

chadb@Chad-2in1 ~/code/videospeed-refactoring (master) 21:12:45  
$ export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]`__git_ps1`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

chadb@Chad-2in1 ~/code/videospeed-refactoring (master) 21:13:03
$ env |grep PS1
PS1=\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\] (master)\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\$

Escaped

chadb@Chad-2in1 ~/code/videospeed-refactoring (master) 21:19:42
$ export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]\`__git_ps1\`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

chadb@Chad-2in1 ~/code/videospeed-refactoring (master) 21:20:02
$ env |grep PS1
PS1=\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]`__git_ps1`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\$

As you can see, __git_ps1 is preserved in the second version whereas in the first it shows the value of __get_ps1 at the time of entry.

On with my original answer.

Explanation

The problem is when the shell executes your command, it performs substitution prior to insertion so the substituted values are inserted rather than your raw command.

In order to fix this, you simply need to use the escape char \ to prevent substitution prior to storing. This results in the unescaped executable version of the command being stored in memory so that bash will re-evaluate it upon each entered command.

The command being unescaped results in it being executed and substituted prior to storage in memory which in turn causes it to only show the command when ~/.bash_profile was first executed/sourced.

Solutions

Original Command

export export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]`__git_ps1`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

Original Stored value of $PS1

Includes string literal " (master) "

\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\] (master) \[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$

Important: To avoid confusion, this stored value only represents the changes to __git_ps1

Fixed Command

export export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]\`__git_ps1\`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

Fixed Stored value of $PS1

This time the command is left in-tact so that it will be substituted upon the next execution.

"\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]`__git_ps1`\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

Backticks are unrelated - proof

As other answers have mentioned you may supply backticks with $(), however, that has nothing to do with the root cause of this issue. These issues should not be conflated.

Old [Not the problem]

`__git_ps1`

New [Not the problem]

$(__git_ps1)

Original command without backticks unescaped [Not the problem, equally broken]

export export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]$(__git_ps1)\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

Original command without backticks escaped [Fixed same way, with escaping]

export export PS1="\\n\[\033[38;5;246m\]\u@\[\033[38;5;245m\]\h\[\033[38;5;15m\] \[\033[38;5;28m\]\w\[\033[38;5;15m\]\[\033[38;5;2m\]\$(__git_ps1)\[\033[38;5;15m\] \[\033[38;5;90m\]\t\[\033[38;5;15m\] \[\033[38;5;232m\]\[\033[38;5;15m\] \n\[\033[38;5;0m\]\\$ "

Simplified proof

To simplify this, let's remove a lot of variables by making our custom shell command that does a similar thing to bash by merely echoing our current working directory to the console using a file sub_demo as the storage medium to show the in-between state.

Unescaped $PWD [the problem]

[07:18 PM] ~/code $ echo $PWD
/c/Users/chadb/code
[07:36 PM] ~/code $ echo $PWD > sub_demo
[07:36 PM] ~/code $ cat sub_demo
/c/Users/chadb/code

Notice that the substituted value of $PWD has been stored in the file, thus destroying the information of what command was used to retrieve that directory. If we merely read out the contents of sub_demo from here on out, it will always result in the same rendered value of /c/Users/chadb/code

This is silly to do, but for illustration purposes I will read the contents of the file from multiple locations to prove the point that this is in fact not going to change the information stored within.

[08:01 PM] ~/code $ cat ~/code/sub_demo 
/c/Users/chadb/code
[08:01 PM] ~ $ cat ~/code/sub_demo
/c/Users/chadb/code
[08:01 PM] ~/code/sub_demo_folder $ cat ~/code/sub_demo 
/c/Users/chadb/code

Notice the value doesn't change, irrespective of the folder. The contents of ~/code/sub_demo are from here on out /c/Users/chadb/code until we change them.

Escaped $PWD [the solution]

Now consider if you instead escape the $ character via \$.

[08:06 PM] ~/code $ echo \$PWD > sub_demo
[08:06 PM] ~/code $ cat sub_demo
$PWD

As you can see, the value is now stored as the command - the same one which is necessary to find our working directory. How is this useful? Well, bash executes whatever the stored value of $PS1 is. Let's simulate that by merely turning the contents of the file executable via adding echo to the beginning.

For completeness and contrast, I will show the unescaped and escaped versions.

[Unescaped]

[08:06 PM] ~/code $ echo "echo $PWD" > sub_demo
[08:11 PM] ~/code $ cat sub_demo
echo /c/Users/chadb/code

[Escaped]

[08:11 PM] ~/code $ echo "echo \$PWD" > sub_demo
[08:13 PM] ~/code $ cat sub_demo
echo $PWD

This isn't that exciting yet, but the real magic happens when you execute the output.

Final simplified proof

Unescaped - Does not work
[08:15 PM] ~/code $ echo "echo $PWD" > sub_demo
[08:15 PM] ~/code $ cat sub_demo
echo /c/Users/chadb/code
[08:15 PM] ~/code $ cd sub_demo_folder/
[08:15 PM] ~/code/sub_demo_folder $ cat ~/code/sub_demo | bash -
/c/Users/chadb/code
[08:15 PM] ~/code/sub_demo_folder $ cd ~
[08:15 PM] ~ $ cat ~/code/sub_demo | bash -
/c/Users/chadb/code
[08:15 PM] ~ $ cd code
[08:15 PM] ~/code $ cat ~/code/sub_demo | bash -
/c/Users/chadb/code

Observe that the output value is always /c/Users/chadb/code

Escaped - Does work
[08:15 PM] ~/code $ echo "echo \$PWD" > sub_demo
[08:15 PM] ~/code $ cat sub_demo
echo $PWD
[08:16 PM] ~/code $ cd sub_demo_folder/
[08:19 PM] ~/code/sub_demo_folder $ cat ~/code/sub_demo | bash -
/c/Users/chadb/code/sub_demo_folder
[08:19 PM] ~/code/sub_demo_folder $ cd ~
[08:19 PM] ~ $ cat ~/code/sub_demo | bash -
/c/Users/chadb
[08:19 PM] ~ $ cd code/
[08:19 PM] ~/code $ cat ~/code/sub_demo | bash -
/c/Users/chadb/code

Observe that now, every single time the command is executed it shows the correct current directory as desired.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .