Bash ${VAR//search/replace} and weird regex behaviour

bashvariable substitution

I am trying to do some searching and replacing on a variable using the ${VAR//search/replace} parameter expansion. I have a pretty long and evil PS1, that I want to work out the size of after expansion. To do so I have to remove a bunch of escape sequences I stuff into it. However on trying to remove all the ANSI CSI SGR sequences I've fell across an issue with my syntax.

Given my PS1 of:

PS1=\[\033]0;[\h] \w\007\]\[\033[1m\]\[\033[37m\](\[\033[m\]\[\033[35m\]\u@\[\033[m
\]\[\033[32m\]\h\[\033[1m\]\[\033[37m\]\[\033[1m\])\[\033[m\]-\[\033[1m\](\[\033[m
\]\t\[\033[37m\]\[\033[1m\])\[\033[m\]-\[\033[1m\](\[\033[m\]\[\033[36m\]\w\[\033[1m
\]\[\033[37m\])\[\033[35m\]${git_branch}\[\033[m\]\n$

(yes it's sick I know…)

I'm trying to do:

# readability
search='\\\[\\033\[[0-9]*m\\\]'
# do the magic
sane="${PS1//$search/}"

However these seems to be greedy at the point of [0-9] (almost like [0-9] is treated like a . instead):

echo "${PS1//$search/}"
\[\033]0;[\h] \w\007\]\n$ 

If I remove the *, and change [0-9] to [0-9][0-9] (as that is more illustrative) I get closer to the expected result:

$ search='\\\[\\033\[[0-9][0-9]m\\\]'
$ echo "${PS1//$search/}"
\[\033]0;[\h] \w\007\]\[\033[1m\](\[\033[m\]\u@\[\033[m\]\h\[\033[1m
\]\[\033[1m\])\[\033[m\]-\[\033[1m\](\[\033[m\]\t\[\033[1m\])\[\033[m\]-\[\033[1m
\](\[\033[m\]\w\[\033[1m\])$(git_branch)\[\033[m\]\n$ 

Why is the * (zero or more) doing crazy things? am I missing something here? If I pass the same regex through sed I get the expected result:

echo $PS1 | sed "s/$search//g"
\[\033]0;[\h] \w\007\](\u@\h)-(\t)-(\w)$(git_branch)\n$

Best Answer

Sounds to me you want to remove things between \[ and \]:

$ shopt -s extglob
$ printf '%s\n' "${PS1//\\\[*(\\[^]]|[^\\])\\\]/}"
(\u@\h)-(\t)-(\w)${git_branch}\n$

However, bash substitution is so inefficient that you would probably be better off firing perl or sed here, or do it in a loop like:

p=$PS1 np=
while :; do
  case $p in
    (*\\\[*\\\]*) np=$np${p%%\\\[*};p=${p#*\\\]};;
    (*) break;;
  esac
done
np=$np$p
printf '%s\n' "$np"

(that's standard POSIX sh syntax above, BTW).

And if you want the expanded prompt from that:

ep=$(PS4=$np;exec 2>&1;set -x;:); ep=${ep%:}
Related Question