50

I have this in my .bashrc:

LIGHTGREEN="\[\033[1;32m\]"
LIGHTRED="\[\033[1;31m\]"
WHITE="\[\033[0;37m\]"
RESET="\[\033[0;00m\]"

function error_test {
    if [[ $? = "0" ]]; then
        echo -e "$LIGHTGREEN"
    else
        echo -e "$LIGHTRED"
    fi
}

PS1="\u\$(error_test)@\w$RESET \$ "

This seems to make the shell output exactly:

username\[\]@~/

The escaping [ and ] around the color codes are showing up in my prompt. If I remove the escape codes from around the colors it works, but then bash line wrapping fails stupendously.

Note if do PS1="LIGHTGREEN - whatever - $RESET" it works and the [ and ] are not escaped. However, I want to do this inside a function, which seems to be the issue.

I can't find any good documentation on this. man echo doesn't even list a -e option. Bash seems like it has a lot of undocumented, handmedown knowledge.

Andy Ray
  • 30,372
  • 14
  • 101
  • 138
  • `help echo` lists several bash-specific options. – Ignacio Vazquez-Abrams Jul 06 '11 at 06:15
  • 1
    And the reason this is so is that the manual pages are for the external variant. You have `/bin/echo` which is documented in `man 1 echo`, and you have Bash's builtin function `echo` which is documented in `help echo`. See `help help` and `man bash` for more on this. – tripleee Aug 20 '11 at 09:39
  • 6
    Welcome to 2017! For future travelers, the simplest answer is: http://stackoverflow.com/a/43462720/746890. (i.e. Just swap `\[` for `\001` and `\[` for `\002`.) – Chris Nolet Apr 22 '17 at 00:46

6 Answers6

61

I found this topic looking for answer how to set bash color with escaping \[ \] from bash function.

Actually there is solution. Bash allows to generate PS1 prompt each time prompt is rendered.

set_bash_prompt(){
    PS1="\u@\h $(call_your_function) $>"
}

PROMPT_COMMAND=set_bash_prompt

This way, PS1 will be interpreted each time prompt will be displayed, so it will call function and render properly all escaping sequences including \[ \] which are important for counting length of prompt (e.g. to make command history work correctly).

Hopefully this will help someone, as I spend half a day to solve this issue.

thedk
  • 1,939
  • 1
  • 20
  • 22
  • 1
    This is the solution. See @Ignacio Vazquez-Abrams earlier answer for why it works. – joemaller Jan 09 '13 at 13:43
  • Why do you prefer using `PROMPT_COMMAND` to putting the function call in `PS1`? – l0b0 Dec 05 '13 at 13:07
  • 5
    @l0b0 The usage of `PROMPT_COMMAND` is needed if you play with colors in the `call_your_function`. Otherwise, length of prompt is not counting right and command history wraps badly. – Lætitia Dec 18 '13 at 18:59
  • 1
    @Tonin No, you can use colours properly in `PS1` commands. [Example](https://github.com/l0b0/tilde/blob/54ee46d965eb0ec4700439dd55cdf5bf387239b8/.bashrc#L60) – l0b0 Dec 19 '13 at 08:24
  • @l0b0 The example you are giving breaks history line wrapping (using ^-r) when the previous command returns an error (ie: when your `printf` is triggered). – Lætitia Dec 19 '13 at 15:35
  • @Tonin Ah, I think I see. When I run `false`, then press Ctrl-r, search for "false", and then press Enter, the command line is shown as `$ false false`, even though only `false` was run. Is that what you mean? And if that's the case, does that mean the length of `PS1` is *not* counted when searching, but at some earlier time? – l0b0 Dec 19 '13 at 16:55
  • @Tonin Looks like [neither solution works](http://unix.stackexchange.com/questions/105926/how-to-include-commands-in-bashs-ps1-without-breaking-line-length-calculation). – l0b0 Dec 19 '13 at 17:28
  • 2
    This works great, thanks! Unfortunately, it breaks Terminal.app's ability to open new tabs rooted to the active tab's path. This ability can be restored like so: `PROMPT_COMMAND="set_bash_prompt; $PROMPT_COMMAND"` See http://superuser.com/a/623305/75328 for more details. – Chad von Nau Feb 22 '14 at 02:27
  • This works great! Helped fix a issue I had trying to insert a newline after each command output, which was breaking the prompt if I had a long path. Just added an extra 'echo' at the end of the function and now it's working like it should! Thanks! – drmrgd Mar 03 '14 at 03:10
39

Use \001 instead of \[ and \002 instead of \], and be aware of the consequences of usingPROMPT_COMMAND as that method will reset the prompt every single time (which can also be just what you want).

The solution for bash prompt echoing colors inside a function is explained here:

The \[ \] are only special when you assign PS1, if you print them inside a function that runs when the prompt is displayed it doesn't work. In this case you need to use the bytes \001 and \002

There is also this other answer that points in the same direction:

bash-specific \[ and \] are in fact translated to \001 and \002

Setting PS1 inside a function called by PROMPT_COMMAND as suggested in the accepted aswer resets PS1 every single time not allowing other scripts to easily modify your promtp (for example Python virtualnenv activate.sh):

$ echo $PS1
<your PS1>
$ PS1="(TEST)$PS1"
$ echo $PS1
<(TEST) is not prepended to PS1 if you are using PROMPT_COMMAND as it is reset>
MauricioRobayo
  • 2,207
  • 23
  • 26
  • 5
    Holy cow – I can't believe this question is 6 years old, and by some luck, you posted the only working solution three days ago! Thanks so much. This needs more upvotes :) – Chris Nolet Apr 22 '17 at 00:32
  • 3
    This solution worked to me in conjunction with using `printf` instead of `echo`. – LostMyGlasses Oct 31 '17 at 09:24
  • Oh my goodness, you're a hero. This worked perfectly and immediately, while also helping me actually understand what was going wrong. Thank you so much. – Mark Aug 26 '19 at 00:32
  • A good question would be - why does everyone( PS1 tutorials) say to escape colors with `\[` instead of teaching more versatile `\001` right away. Edit: I was wrong, ofc Arch Linux tutorial mentions it right away! :D – Yurkee Nov 13 '20 at 10:45
8

\[ and \] must be used in $PS* directly, rather than just having them output via echo.

LIGHTGREEN="\033[1;32m"
LIGHTRED="\033[1;31m"
WHITE="\033[0;37m"
RESET="\033[0;00m"

function error_test {
    if [[ $? = "0" ]]; then
        echo -e "$LIGHTGREEN"
    else
        echo -e "$LIGHTRED"
    fi
}

PS1="\u\[\$(error_test)\]@\w\[$RESET\] \$ "
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
  • 3
    What about the case where a function echoes more than a color? What if it outputs `echo -e "$LIGHTGREEN some stuff $RESET"` ? This solution - wrapping the function call in `\[` and `\]` - seems to have the same problem – Andy Ray Jul 06 '11 at 06:15
  • That's why you can't do it that way. – Ignacio Vazquez-Abrams Jul 06 '11 at 06:15
  • You're saying that it's impossible in bash to have a function that echoes colors and text? – Andy Ray Jul 06 '11 at 06:34
  • There must be another way - I have some fairly complicated logic that I want to use to build my bash prompt, too lengthy to put all into one PS1= line, so I put it in a function. I don't even know if it could realistically be put into one line. I would like to colorize the output with multiple colors. – Andy Ray Jul 06 '11 at 06:50
  • My own [.bashrc](https://github.com/l0b0/tilde/blob/master/.bashrc) does use functions and multiple colors in `$PS1`. It Just Works (TM). – l0b0 Jul 13 '11 at 07:52
  • Of course there is a way: `echo -e "\001${red}\002"`. – FelipeC Aug 09 '22 at 20:46
0

I realize this is an old topic, but I just got this working with functions. The trick is to split the printing and non-printing parts of the function up so you can correctly bracket the non-printing parts with [ ]. Normally I like my ERROR.. line to be separate (and this isn't a problem then), but this also works correctly if everything is all in one line.

Note that I return the previous $? value from each sub-shell so $? gets propagated from one to the next.

PS1="\n\
\[\`
  cja_prv_retval=\$?;
  if [ \$cja_prv_retval != 0 ];
     then echo -ne \$E_ERROR;
  fi
  exit \$cja_prv_retval
\`\]\
\`
  cja_prv_retval=\$?;
  if [ \$cja_prv_retval != 0 ];
     then echo -ne \"ERROR: RETURN CODE \$cja_prv_retval\";
  fi
  exit \$cja_prv_retval
\`\
\[\`
  cja_prv_retval=\$?;
  if [ \$cja_prv_retval != 0 ];
     then echo -ne \$E_RESET;
  fi
  exit \$cja_prv_retval
\`\]\
${P_RESET}${P_GRAY}\! \t ${P_RED}\u${P_GRAY}@${P_GREEN}\h ${P_YELLOW}\w ${P_CYAN}   ══>${P_RESET} "

This gives me either

2021 12:28:05 cja@morpheus04 ~ ══>

if there is no error, or

ERROR: RETURN CODE 1 2021 12:28:16 cja@morpheus04 ~ ══>

if there is an error. Everything is correctly spaced (multi-line history editing works correctly).

  • You can also see how I did it in my complete function http://andrewray.me/bash-prompt-builder/index.html – Andy Ray May 18 '12 at 18:30
  • The return code chaining trick helped me solve a puzzle with my prompt; previously, I could either safely bracket color codes for sane editing, or have a function generate different prompts based on the last exit code, but not both due to the subshell making passing variables impossible. – Tangent 128 Nov 10 '12 at 16:26
0

This will work fine.

LIGHTGREEN="\e[32m"
LIGHTRED="\e[31m"
RESET="\e[0m"

error_test () {
    if [[ $? = "0" ]]; then
        echo -e "$LIGHTGREEN"
    else
        echo -e "$LIGHTRED"
    fi
}
export PS1=$(printf "$(error_test) $(whoami)@${RESET}$(pwd) ")
Luis Lavaire.
  • 599
  • 5
  • 17
0

Here's the coloured exit code portion of my PS1 code:

color_enabled() {
    local -i colors=$(tput colors 2>/dev/null)
    [[ $? -eq 0 ]] && [[ $colors -gt 2 ]]
}

BOLD_FORMAT="${BOLD_FORMAT-$(color_enabled && tput bold)}"
ERROR_FORMAT="${ERROR_FORMAT-$(color_enabled && tput setaf 1)}"
RESET_FORMAT="${RESET_FORMAT-$(color_enabled && tput sgr0)}"

# Exit code
PS1='$(exit_code=$?; [[ $exit_code -eq 0 ]] || printf %s $BOLD_FORMAT $ERROR_FORMAT $exit_code $RESET_FORMAT " ")'

Screenshot (with one Subversion repository path anonymized): Color coded output

l0b0
  • 55,365
  • 30
  • 138
  • 223
  • 1
    Grargh, stupid Stackoverflow comments. Let's try this again: I tried your method and it seems to have the same problem mine does, which is that bash line wrapping breaks. Typing past the end of a line makes the text wrap onto the same line. Am I doing something wrong? : https://gist.github.com/1071081 – Andy Ray Jul 08 '11 at 03:38
  • To clarify you're edit: your solution is now to put the entire contents of the function in a string, and make the prompt execute that string, yes? – Andy Ray Jul 11 '11 at 06:32
  • also, if I try to make exit code a function in a string, and do $(${exit_code}), the code stays the same. is that because other commands like tput are resetting it? – Andy Ray Jul 11 '11 at 07:14
  • Re. your first comment: Yes, try to remove the last line and `echo "$ps1_command"`. You must pack functionality in a string if you want it to be run on each display of the prompt, instead of only once at login. – l0b0 Jul 11 '11 at 13:07
  • Re. your second comment: I don't understand. What *exactly* are you doing? – l0b0 Jul 11 '11 at 13:09
  • Thanks! This answer was a brilliant starter to writing my own prompt customizations! – Cobra_Fast Aug 28 '12 at 15:49
  • @AndyRay: [I don't use that anymore](https://github.com/l0b0/tilde/blob/master/.bashrc), but it was to make sure that I don't get any `Undefined variable` errors - `${ps1_command:-}` means "if the variable `ps1_command` is empty or undefined, substitute the empty string, otherwise substitute the value of `ps1_command`". – l0b0 Aug 28 '12 at 20:53
  • @AndyRay The line wrapping and command history issue doesn't seem to be solved with this answer nor with your Bash Prompt Builder. The answer from @ thedk was the only one working for me. – Lætitia Dec 18 '13 at 19:06
  • @Tonin Can you demonstrate how it's not solved? I'd love to know about it if I have bugs in my `.bashrc`. – l0b0 Dec 19 '13 at 08:26
  • @l0b0 Using your piece of code in my .bashrc, when I do `ctrl-r` to go back into history, with an exit code > 0, the prompt gets eaten on the right (ie: character count is not correct). – Lætitia Dec 19 '13 at 15:40