bash command substitution, functions and quoting : need help

bash command substitution, functions and quoting : need help

am 05.04.2008 19:01:47 von amwyll.codydd

Hello everybody.

I've got a question that's been driving me mad for a couple of days
now. It has to do with quoting, command substitution and functions.
I'm trying to generate a format string for PS1 within a function,
return that string from function (via echo) and assign THAT to PS1,
except for some reason the escaping goes all awry in the process.

Please note the prompts (everything below ">") in the below commands
execution sequence with lines numbered on the left (not part of prompt
per se) :

01 prompt> PS1='$( { echo "\[\033[1;34m\]$(date) >\[\033[0m\]
" ; } )'
02 Sat Apr 5 15:01:29 2008 >
03 Sat Apr 5 15:01:30 2008 >
04 Sat Apr 5 15:01:31 2008 > func() { echo "\[\033[1;34m\]$(date) >\
[\033[0m\] " ; } ; PS1='$(func)'
05 \[\033[1;34m\]Sat Apr 5 15:01:37 2008 >\[\033[0m\]
06 \[\033[1;34m\]Sat Apr 5 15:01:39 2008 >\[\033[0m\]
07 \[\033[1;34m\]Sat Apr 5 15:01:40 2008 >\[\033[0m\]

At #01 I assign the output of command substitution directly to PS1,
and as can be observed in #02-#04 it works out perfect (the coloring
worked, date evaluated every time, etc). At #04 I try to assign the
same "echo" output to PS1 but this time "echo" was executed in a
function. As can be observed in #05-#07 the escape sequences were
somehow "garbled" and instead of doing coloring they are reproduced
verbatim. I'm observing this with bash 2.05b and 3.x.

Any ideas as to how I can format the string including bash prompt
color escape sequences inside a function, using bash built-ins only
(e.g. no printf and such), then somehow have the function evaluated
every time and have the output stored into PS1, with all the escape
sequences intact?

Thanks in advance.

Re: bash command substitution, functions and quoting : need help

am 05.04.2008 20:05:14 von Icarus Sparry

On Sat, 05 Apr 2008 10:01:47 -0700, amwyll.codydd wrote:

> Hello everybody.

>
> Any ideas as to how I can format the string including bash prompt color
> escape sequences inside a function, using bash built-ins only (e.g. no
> printf and such),

printf is a builtin in bash.

Re: bash command substitution, functions and quoting : need help

am 05.04.2008 20:49:05 von PK

amwyll.codydd@gmail.com wrote:

> 01 prompt> PS1='$( { echo "\[\033[1;34m\]$(date) >\[\033[0m\]
> " ; } )'
> 02 Sat Apr 5 15:01:29 2008 >
> 03 Sat Apr 5 15:01:30 2008 >
> 04 Sat Apr 5 15:01:31 2008 > func() { echo "\[\033[1;34m\]$(date) >\
> [\033[0m\] " ; } ; PS1='$(func)'
> 05 \[\033[1;34m\]Sat Apr 5 15:01:37 2008 >\[\033[0m\]
> 06 \[\033[1;34m\]Sat Apr 5 15:01:39 2008 >\[\033[0m\]
> 07 \[\033[1;34m\]Sat Apr 5 15:01:40 2008 >\[\033[0m\]
>
> At #01 I assign the output of command substitution directly to PS1,
> and as can be observed in #02-#04 it works out perfect (the coloring
> worked, date evaluated every time, etc).At #04 I try to assign the
> same "echo" output to PS1 but this time "echo" was executed in a
> function. As can be observed in #05-#07 the escape sequences were
> somehow "garbled" and instead of doing coloring they are reproduced
> verbatim. I'm observing this with bash 2.05b and 3.x.
>
> Any ideas as to how I can format the string including bash prompt
> color escape sequences inside a function, using bash built-ins only
> (e.g. no printf and such), then somehow have the function evaluated
> every time and have the output stored into PS1, with all the escape
> sequences intact?

Bash has built-in notations to show date, time, hostname, current directory,
etc. in the prompt. See man bash, section "PROMPTING".

For example, if you want the date and the time in your prompt, try this:

$ PS1='\d \t \$'
Sat Apr 05 19:53:35 $

Other formats are possible, see the description for \D.

Regarding colors (blue in your case), I usually do something like this, for
better readability:

BLUE='\[\033[01;34m\]'
GREEN='\[\033[01;32m\]'
NORMAL='\[\033[00m\]'
export PS1="$GREEN[\d \t $BLUE\w]\\$ $NORMAL"

To use the same concept in a function, you can do

func () {
BLUE='\[\033[01;34m\]'
GREEN='\[\033[01;32m\]'
NORMAL='\[\033[00m\]'

# printf is sometimes better than echo
printf "%s\n" "$GREEN[\d \t $BLUE\w]\\$ $NORMAL"
}

and then

$ PS1=$(func)
[Sat Apr 05 20:01:35 ~]$ # in green, the ~]$ in blue

In your specific case, You can do this:

PS1="\[\033[1;34m\]\$(date) >\[\033[0m\]"

(note the backslash before $(date)). This way, "date" is executed each time,
and the output is substituted in the prompt.

If you want to put the above in a function, you can do this:

func() {
echo "\[\033[1;34m\]\$(date) >\[\033[0m\]"

# or, better readable
#BLUE='\[\033[01;34m\]'
#NORMAL='\[\033[00m\]'
#echo "$BLUE\$(date) >$NORMAL"

# again, note the \ before $(date)
}

$ PS1=$(func)
Sat Apr 5 20:06:06 CEST 2008 > # in blue

However, as I said before, inserting the $(date) command substitution in the
prompt isn't really necessary, since you can use the bash builtin \d or
similar.

Your first example sets PS1 once, and the string assigned to PS1 contains
the escape sequences as well as $(date), so bash executes "date" it each
time it wants to display the prompt, but retains and interprets the escape
sequences, producing correct colors. In your second example, PS1 contains
the string "$(func)" only, and you are asking bash to execute func() each
time, which is a different thing. func() outputs, for instance

\[\033[01;34m\]Sat Apr 5 20:28:42 CEST 2008 >\[\033[00m\]

It could have output "foobarblahblah" and it would have been the same thing:
just a raw string assigned to PS1. Bash does not re-evaluate the escape
characters.

If you want a fancy prompt, I suggest you read the Bash prompt HOWTO:

http://tldp.org/HOWTO/Bash-Prompt-HOWTO/

--
All the commands are tested with bash and GNU tools, so they may use
nonstandard features. I try to mention when something is nonstandard (if
I'm aware of that), but I may miss something. Corrections are welcome.

Re: bash command substitution, functions and quoting : need help

am 06.04.2008 00:45:49 von amwyll.codydd

On Apr 5, 9:05=A0pm, Icarus Sparry wrote:
> printf is a builtin in bash.

Oh! Thanks for the heads up - apparently I somehow missed the fact
that printf got added as a built-in, even though it seems to have
happened a serious while ago (sometime post 2.01 according to
changelog).

Unfortunately this doesn't solve my original problem - it doesn't work
with printf either. :)

Thanks anyway, though, now I can stop restraining myself as far as
printf goes!

A.C.

Re: bash command substitution, functions and quoting : need help

am 06.04.2008 01:45:09 von amwyll.codydd

First of all - thanks for the detailed reply, pk.

On Apr 5, 9:49=A0pm, pk wrote:
> amwyll.cod...@gmail.com wrote:
>
> > Any ideas as to how I can format the string including bash prompt
> > color escape sequences inside a function, using bash built-ins only
> > (e.g. no printf and such), then somehow have the function evaluated
> > every time and have the output stored into PS1, with all the escape
> > sequences intact?
>
> Bash has built-in notations to show date, time, hostname, current director=
y,
> etc. in the prompt. See man bash, section "PROMPTING".

I know, I know. I'm not REALLY trying to have full "date" output as my
prompt, :) I'm trying to hack up an informative prompt that includes
some built-in prompt escapes like host, but also multiple bits of
output of several external commands whose output is expected to change
from time to time and which are expensive enough for me to want to
invoke at most ONCE per prompt generation - hence doing so inside a
function (see below).

> Regarding colors (blue in your case), I usually do something like this, fo=
r
> better readability:

Thanks, I know, but that's not the issue as you shall see in a moment.

> To use the same concept in a function, you can do
>
> func () {
> =A0 BLUE=3D'\[\033[01;34m\]'
> =A0 GREEN=3D'\[\033[01;32m\]'
> =A0 NORMAL=3D'\[\033[00m\]'
>
> =A0 # printf is sometimes better than echo
> =A0 printf "%s\n" "$GREEN[\d \t $BLUE\w]\\$ $NORMAL"
>
> }
>
> and then
>
> $ PS1=3D$(func)
> [Sat Apr 05 20:01:35 ~]$ =A0 # in green, the ~]$ in blue

A-ha! THAT is an issue : when you put it that way, func() is only
invoked ONCE, it's "output" is expanded and stored into PS1. As I
mentioned in my original post, however, I need to have that func
evaluated EVERY time prompt is being displayed.

> (note the backslash before $(date)). This way, "date" is executed each tim=
e,

See above. Unfortunately this trick won't work in my case since I need
to parse the output of external command(s) each time, extract some
info from it, and tuck that into the prompt in several places,
interspersed with color escape sequences and other data...

> If you want to put the above in a function, you can do this:

> =A0 # again, note the \ before $(date)
>
> }
>
> $ PS1=3D$(func)
> Sat Apr =A05 20:06:06 CEST 2008 > =A0 # in blue

Yup, same problem - I need the func to be evaluated every time.

> Your first example sets PS1 once, and the string assigned to PS1 contains
> the escape sequences as well as $(date), so bash executes "date" it each
> time it wants to display the prompt, but retains and interprets the escape=

> sequences, producing correct colors. In your second example, PS1 contains
> the string "$(func)" only, and you are asking bash to execute func() each

Well, yes, I WANT func() to be executed every time!

> time, which is a different thing. func() outputs, for instance
>
> \[\033[01;34m\]Sat Apr =A05 20:28:42 CEST 2008 >\[\033[00m\]
>
> It could have output "foobarblahblah" and it would have been the same thin=
g:
> just a raw string assigned to PS1. Bash does not re-evaluate the escape
> characters.

Could you please elaborate on that particular point? I somehow assumed
that bash re-evaluates escape sequences every time it consults PS1
before outputting the prompt. The following output seems to partially
confirm this since in lines #02-#04 and #06 below prompts (ASIDE from
leading "\[" and trailing "\]" - which is the problem I'm trying to
solve!) is painted light blue, but bash is completely confused as to
prompt width... (Hats off to Icarus Sparry on the printf thing, but
"echo -e" yields exactly the same output).

01 prompt >func() { printf "\[\033[1;34m\]$(date) >\[\033[0m
\]" ; } ; PS1=3D'$(func)'
02 \[\]Sun Apr 6 02:24:16 IDT 2008 >\[\]
03 \[\]Sun Apr 6 02:24:17 IDT 2008 >\[\]
04 \[\]Sun Apr 6 02:24:18 IDT 2008 >\[\]echo $PS1
05 $(func)
06 \[\]Sun Apr 6 02:24:22 IDT 2008 >\[\]

> If you want a fancy prompt, I suggest you read the Bash prompt HOWTO:
>
> http://tldp.org/HOWTO/Bash-Prompt-HOWTO/

Thanks, I have, before posting here. That's where I stole the coloring
and "\[" ideas. :) Unfortunately, it doesn't seem to answer the
question I have, which is why do SOME escape sequences go awry if
they're output from a function, namely "\[" and "\]" cease being
recognized as "special" escape sequences, but color escape sequences
work out fine... :(

Thanks again, pk.

A.C.

Re: bash command substitution, functions and quoting : need help

am 06.04.2008 12:10:36 von PK

amwyll.codydd@gmail.com wrote:

> Well, yes, I WANT func() to be executed every time!

Ok. It would be nice of you provided some examples of what you're trying to
do.

But what you probably want is to have all the programs invoked by func()
executed each time, rather than func() itself.

Suppose you want to insert into the prompt the output of two different
external commands (these are only examples, of course): "date +%T"
and "cat /proc/loadavg". The output of "date +%T" should be in blue, the
output of "cat /proc/loadavg" in green.

func() {
BLUE='\[\033[1;34m\]'
GREEN='\[\033[01;32m\]'
NORMAL='\[\033[0m\]'
printf "%s\n" "$BLUE\$(date +%T) $GREEN\$(cat /proc/loadavg) >$NORMAL"
}

If you do

PS1=$(func)

PS1 contains the escape sequences with embedded commands, and both commands
are executed at each prompt. Is this acceptable for you? If not, provide
some example.

>> thing: just a raw string assigned to PS1. Bash does not re-evaluate the
>> escape characters.
>
> Could you please elaborate on that particular point? I somehow assumed
> that bash re-evaluates escape sequences every time it consults PS1
> before outputting the prompt.

No, whatever is output by the function is taken as a string. You don't even
need to take into account escape characters to see that. Try this:

func () {
printf "%s\n" '$(date) > '
}

$ PS1=$(func)
Sun Apr 6 11:56:59 CEST 2008 > # <----- the new prompt
Sun Apr 6 11:57:02 CEST 2008 > echo "$PS1"
$(date) # <----- the value of PS1

Here, bash executes "date" each time and places the output in the prompt,
just as we want. Now do the following:

Sun Apr 6 11:57:07 CEST 2008 > PS1='$(func)'
$(date) > # <----- this is the prompt: a literal string "$(date) > "
$(date) >
$(date) > echo "$PS1"
$(func) # <----- the value of PS1 is $(func), not $(date)
$(date) >

Now, once bash evaluates PS1 and executes func(), the ouput ("$(date)")is
put verbatim in the prompt.

> and "\[" ideas. :) Unfortunately, it doesn't seem to answer the
> question I have, which is why do SOME escape sequences go awry if
> they're output from a function, namely "\[" and "\]" cease being
> recognized as "special" escape sequences, but color escape sequences
> work out fine... :(

Try the above methods and see if they are appropriate for you; otherwise,
provide specific examples.

Regards

--
All the commands are tested with bash and GNU tools, so they may use
nonstandard features. I try to mention when something is nonstandard (if
I'm aware of that), but I may miss something. Corrections are welcome.

Re: bash command substitution, functions and quoting : need help

am 07.04.2008 06:54:35 von amwyll.codydd

On Apr 6, 1:10=A0pm, pk wrote:
> amwyll.cod...@gmail.com wrote:
> > Well, yes, I WANT func() to be executed every time!
>
> Ok. It would be nice of you provided some examples of what you're trying t=
o
> do.

Sure. Let's pretend we have this database query tool called "uptime".
It has certain invocation overhead - not too noticeable if called just
once per prompt, but at twice or more per prompt the shell starts to
"lag". We want to extract a few bits of info from its output and have
those, with some coloring to emphasize the more important bits, in our
prompt. the whole thing might look along the lines of :

prompt> cat uptime_prompt.sh
db_query()
{
local all time users la days
# here we query the DB :
all=3D$(uptime)

# parse DB query output :
time=3D${all%% up*}
la=3D${all##*load average: } ; la=3D${la%%,*}
users=3D${all%% users*} ; users=3D${users##* }
days=3D${all%% days*} ; days=3D${days##* }

# vanilla B&W version :
# printf "[${days}d : ${users}u : $la : $time] \$"

# what we're trying to solve :
local WHITE=3D"\[\033[1;37m\]"
local RED=3D"\[\033[1;31m\]"
local GREEN=3D"\[\033[1;32m\]"
local NOCOLOR=3D"\[\033[0m\]"
printf "$WHITE[$GREEN${days}d$NOCOLOR : $RED${users}u
$NOCOLOR : $la : $time$WHITE]\$$NOCOLOR "
}

[ "$PS1" ] && PS1=3D'$(db_query)'
prompt>
prompt> . ./uptime_prompt.sh
\[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:10\[\]]$\[\]
\[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:12\[\]]$\[\]
\[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:14\[\]]$\[\]

The last 3 bash prompts are colored appropriately, but for some reason
the "\[" and "\]" escape chars are not interpreted by bash - they're
left in the prompt verbatim and the prompt line is wrapped at wrong
width.

What i'm trying to do is to have bash interpret not only the color
escape sequences in the above example (which it does re-evaluate with
every prompt!), but also "\[" and "\]" non-printable chars start/stop
escape sequences (which, for some reason, it doesn't).

> But what you probably want is to have all the programs invoked by func()
> executed each time, rather than func() itself.

I want the program(s) invoked by func() to be executed ONCE PER
PROMPT, but after that I want to parse the output of the program(s) in
question, split it, soap it up, color it and format it anew. I somehow
assumed that func() would be a good place to do all of those, but I'm
open to suggestions.

> Suppose you want to insert into the prompt the output of two different
> external commands (these are only examples, of course): "date +%T"
> and "cat /proc/loadavg". The output of "date +%T" should be in blue, the
> output of "cat /proc/loadavg" in green.
>
> func() {
> =A0 BLUE=3D'\[\033[1;34m\]'
> =A0 GREEN=3D'\[\033[01;32m\]'
> =A0 NORMAL=3D'\[\033[0m\]'
> =A0 printf "%s\n" "$BLUE\$(date +%T) $GREEN\$(cat /proc/loadavg) >$NORMAL"=

>
> }
>
> If you do
>
> PS1=3D$(func)
>
> PS1 contains the escape sequences with embedded commands, and both command=
s
> are executed at each prompt. Is this acceptable for you? If not, provide
> some example.

No, for reasons outlined above I don't think this works out.

> >> thing: just a raw string assigned to PS1. Bash does not re-evaluate the=

> >> escape characters.
>
> > Could you please elaborate on that particular point? I somehow assumed
> > that bash re-evaluates escape sequences every time it consults PS1
> > before outputting the prompt.
>
> No, whatever is output by the function is taken as a string. You don't eve=
n
> need to take into account escape characters to see that. Try this:
>
> func () {
> =A0 printf "%s\n" '$(date) > '
>
> }
>
> $ PS1=3D$(func)
> Sun Apr =A06 11:56:59 CEST 2008 > =A0 =A0 =A0 =A0 =A0 =A0 =A0 # <----- the=
new prompt
> Sun Apr =A06 11:57:02 CEST 2008 > echo "$PS1" =A0 =A0
> $(date) =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =
=A0 =A0 =A0 # <----- the value of PS1
>
> Here, bash executes "date" each time and places the output in the prompt,
> just as we want. Now do the following:
>
> Sun Apr =A06 11:57:07 CEST 2008 > PS1=3D'$(func)'
> $(date) > =A0 =A0# <----- this is the prompt: a literal string "$(date) > =
"
> $(date) > =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0
> $(date) > echo "$PS1"
> $(func) =A0 =A0 =A0# <----- the value of PS1 is $(func), not $(date)
> $(date) >
>
> Now, once bash evaluates PS1 and executes func(), the ouput ("$(date)")is
> put verbatim in the prompt.

I'm sorry, but I'm still not clear on this point - it was you that
explicitly made bash not to re-evaluate date in the above examples by
placing it in single quotes in printf params. I.e.

prompt> func () { printf "%s\n" '$(date) > ' ; } ; PS1=3D'$(func)'
$(date) >

BUT :

prompt> func () { printf "%s\n" "$(date) > " ; } ; PS1=3D'$(func)'
Mon Apr 7 07:42:28 2008 >
Mon Apr 7 07:42:29 2008 >
Mon Apr 7 07:42:31 2008 >

and :

prompt> func () { printf "$(date) > " ; } ; PS1=3D'$(func)'
Mon Apr 7 07:42:39 2008 >
Mon Apr 7 07:42:41 2008 >
Mon Apr 7 07:42:42 2008 >

Also, see my uptime_prompt.sh example at the top of this post. In that
one, bash clearly re-evaluates PS1 each time since you can see the
prompt being changed AND color escape sequences applied to it, despite
PS1 being updated using

PS1=3D'$(db_query)'

The only thing bash refuses to re-evaluate there are the "\[" and "\]"
escape sequences, and I fail to understand why or how to get around
that...

Thanks for taking your time to answer, pk.

A.C.

Re: bash command substitution, functions and quoting : need help

am 07.04.2008 10:38:40 von PK

Amwyll Codydd wrote:

> Sure. Let's pretend we have this database query tool called "uptime".
> It has certain invocation overhead - not too noticeable if called just
> once per prompt, but at twice or more per prompt the shell starts to
> "lag". We want to extract a few bits of info from its output and have
> those, with some coloring to emphasize the more important bits, in our
> prompt. the whole thing might look along the lines of :
>
> prompt> cat uptime_prompt.sh
> db_query()
> {
> local all time users la days
> # here we query the DB :
> all=$(uptime)
>
> # parse DB query output :
> time=${all%% up*}
> la=${all##*load average: } ; la=${la%%,*}
> users=${all%% users*} ; users=${users##* }
> days=${all%% days*} ; days=${days##* }
>
> # vanilla B&W version :
> # printf "[${days}d : ${users}u : $la : $time] \$"
>
> # what we're trying to solve :
> local WHITE="\[\033[1;37m\]"
> local RED="\[\033[1;31m\]"
> local GREEN="\[\033[1;32m\]"
> local NOCOLOR="\[\033[0m\]"
> printf "$WHITE[$GREEN${days}d$NOCOLOR : $RED${users}u
> $NOCOLOR : $la : $time$WHITE]\$$NOCOLOR "
> }
>
> [ "$PS1" ] && PS1='$(db_query)'
> prompt>
> prompt> . ./uptime_prompt.sh
> \[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:10\[\]]$\[\]
> \[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:12\[\]]$\[\]
> \[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:14\[\]]$\[\]
>
> The last 3 bash prompts are colored appropriately, but for some reason
> the "\[" and "\]" escape chars are not interpreted by bash - they're
> left in the prompt verbatim and the prompt line is wrapped at wrong
> width.

Ok, now I see.

See the bash-prompt HOWTO, section 10.1. It does something quite close to
what you want.

Essentially, you can use the PROMPT_COMMAND variable (man bash for the
details) to do the calculations, and then pick up the results in PS1. But
still, the colors must be part of the value of PS1, not provided by an
external command.

You could do eg

func () {
# ... calculate $days, $users, $la and $time ...
}

$ PROMPT_COMMAND=func
$ PS1="$WHITE[$GREEN${days}d$NOCOLOR : $RED${users}u $NOCOLOR : $la :
$time$WHITE]\$$NOCOLOR "

Hope this helps.

> I'm sorry, but I'm still not clear on this point - it was you that
> explicitly made bash not to re-evaluate date in the above examples by
> placing it in single quotes in printf params. I.e.
>
> prompt> func () { printf "%s\n" '$(date) > ' ; } ; PS1='$(func)'
> $(date) >
>
> BUT :
>
> prompt> func () { printf "%s\n" "$(date) > " ; } ; PS1='$(func)'
> Mon Apr 7 07:42:28 2008 >
> Mon Apr 7 07:42:29 2008 >
> Mon Apr 7 07:42:31 2008 >

This works, but not for the same reason.
There are various point where things are evaluated in the above. Let's
proceed step by step. Keep in mind that the value of PS1 is always assigned
ONCE, but evaluated AT EACH PROMPT.

func () { printf "%s\n" "$(date) > " ; } ; PS1=$(func)
Mon Apr 7 09:37:11 CEST 2008 > echo "$PS1"
Mon Apr 7 09:37:11 CEST 2008 >

What happens here is that, to set the value of PS1, bash executes func once.
func executes date, which outputs a string. The output of printf contains
the calculated date, and that's what's assigned to PS1 (once). Thus, PS1
contains a fixed string. At each prompt, bash evaluates PS1 and sets the
prompt as a result of that evaluation. Since PS1 contains a fixed string,
the prompt reflects that string and does not change. This is not what we
want, of course.

func () { printf "%s\n" '$(date) > ' ; } ; PS1=$(func)
Mon Apr 7 09:38:47 CEST 2008 >
Mon Apr 7 09:38:49 CEST 2008 >
Mon Apr 7 09:38:50 CEST 2008 > echo "$PS1"
$(date) >

Here bash executes func once, as before, but func does NOT call the external
command date; instead, it outputs the literal string '$(date) > ', and this
is what's assigned to PS1. When bash has to output the prompt, it looks at
what's in PS1, and sees that it contains a command substitution -
$(date) -; so, it executes that command and assigns the output to the
prompt. Note that the output of the command is NOT assigned to PS1; it's
only used to set the prompt. PS1 retains the value of '$(date) > ' all the
time. As I said before, PS1 is always set ONCE. This is an important point.

Now, let's do what you did:

func () { printf "%s\n" "$(date) > " ; } ; PS1='$(func)'
Mon Apr 7 09:44:07 CEST 2008 >
Mon Apr 7 09:44:08 CEST 2008 > echo "$PS1"
$(func)

Why does this work? Here, when PS1 is set, bash does nothing. It just
assigns the string '$(func)' to PS1. When bash has to output the prompt, it
looks at what's in PS1, and sees that it contains a command substitution -
$(func) -. It then executes func, which executes date, and outputs the
string. This string is used by bash to set the prompt - again, it does NOT
modify the value of PS1 in any way. PS1 is still '$(func)'. So you see
that, whatever is output by func, does NOT modify PS1, which is evaluated
only once.

The difference between the third case and the second is that, in the latter,
at each prompt bash calls func(), and func() executes date and outputs a
fixed string, while in the former it's bash itself that calls date in the
process of evaluating PS1. In both cases, the output returned by the
evaluation process is used to set the prompt. PS1 is NOT re-set nor
re-evaluated.

Why did you see some color in the output then? Let's look better at that.

RED="\[\033[1;31m\]"

Strictly speaking, and as noted in the bash-prompt HOWTO, the ANSI escape
sequence in the above is just the '\033[1;31m' part. The extra brackets are
interpreted by bash to mean "what's inside here is not printable stuff".
Putting the ANSI escape sequence inside brackets helps bash calculate the
correct prompt width. See the bash-prompt HOWTO, section 3.4 "Non-Printing
Characters in Prompts".

However, an escape sequence is still an escape sequence, so, no matter who
outputs it, it does what it have to do, ie, change the color in our case.

What happens with your function above is that you have PS1=$(func), so bash
does not re-evaluate the output of the function (which is a string).
However, that output does get written to the screen, and so the embedded
ANSI escape sequences DO change the colors. But, as you see, the \[ and \]
that are there to tell bash about the nonprinting stuff are NOT interpreted
by bash, so you see them output verbatim.

Here's what your function returns:

"$WHITE[$GREEN${days}d$NOCOLOR : $RED${users}u$NOCOLOR : $la :
$time$WHITE]\$$NOCOLOR "

which, substituting the variables' values, become


"\[\033[1;37m\][\[\033[1;32m\]${days}d\[\033[0m\] : \[\03
[1;31m\]${users}u\[\033[0m\] : $la : $time\[\033[1;37m\]]\$\[\033[0m\]"

(sorry for the wraps, must be all on a single line).

As said, bash does NOT interpret this string, but, since it does contain
ANSI escape sequences, when it's written to the screen the escape sequences
are interpreted by the terminal and change the colors. The output you see
is:

\[\][\[\]158d\[\] : \[\]23u\[\] : 0.00 : 07:02:10\[\]]$\[\]

Analyzing out the output (omitting spaces for clarity), this is what you
see:

\[\] <--- uninterpreted \[ and \] for $WHITE
[ <--- literal [
\[\] <--- uninterpreted \[ and \] for $GREEN
158d <--- $days + "d"
\[\] <--- uninterpreted \[ and \] for $NOCOLOR
: <--- literal :
\[\] <--- uninterpreted \[ and \] for $RED
23u <--- $users + "u"
\[\] <--- uninterpreted \[ and \] for $NOCOLOR
: <--- literal :
0.00 <--- $la
: <--- literal :
07:02:10 <--- $time
\[\] <--- uninterpreted \[ and \] for $WHITE
] <--- literal ]
$ <--- bash's \$
\[\] <--- uninterpreted \[ and \] for $NOCOLOR

What was between each \[ and \] is interpreted by the terminal and removed.
Still, bash plays no part in that.

Hope it's clearer now.

> The only thing bash refuses to re-evaluate there are the "\[" and "\]"
> escape sequences, and I fail to understand why or how to get around
> that...

As you've hopefully seen by now, that's not the only thing bash "refuses" to
re-evaluate.

> Thanks for taking your time to answer, pk.

You're welcome.

--
All the commands are tested with bash and GNU tools, so they may use
nonstandard features. I try to mention when something is nonstandard (if
I'm aware of that), but I may miss something. Corrections are welcome.