Bash prompt variables does not work inside a bash function

I’m working on a prompt customization, but for some reason, when I use the u, h and W variables as is it works perfectly, but when I put them inside a function, they are displayed as “u” or “W” instead of their values.

...

print_user()
{
  echo -e "01u02@01h02"
}

print_dir()
{
  echo -e "01${YELLOW}0201W0201${RESET_ATTR}02"
}

PS1='[$(print_user) on $(print_dir)] $(get_git_repo) 01n02$(print_prompt) '

This displays as:

[u@h on W]
>

If I move them outside of the function like so

PS1='[[u]@[h] [${YELLOW}][w][${RESET_ATTR}]] $(get_git_repo) [n]$(print_prompt)'

It works fine, and displays the current directory with the username and hostname:

[myusername@arch on ~]
>

Is this just how bash works? Is there a different way of doing it so it will work? Why is it that inside of a function it won’t display the variables’ values but outside of a function it does?

Answer

From the man page, under PROMPTING

Bash allows these prompt strings to be customized by inserting a number of backslash-escaped special characters that are decoded as follows:

[…]

After the string is decoded, it is expanded via parameter expansion, command substitution, arithmetic expansion, and quote removal, subject to the value of the promptvars shell option (see the description of the shopt command under SHELL BUILTIN COMMANDS below).

By the time the shell expands $(print_user) to add u to the string, it is too late to decode it, so the literal string u remains in the prompt.

One alternative is to use PROMPT_COMMAND to execute a function that defines PS1 dynamically, just before it is displayed, instead of embedding command substitution in the value of PS1 itself.

make_prompt () {
  PS1="[$(print_user) on $(print_dir)] $(get_git_repo)"
  PS1+='[n]'
  PS1+="$(print_prompt) "
}

PROMPT_COMMAND=make_prompt

Now, print_user will have been called before the shell decodes the value of PS1, by which time all the prompt escapes will be present.