Bash expand variable in a variable
BashBash Problem Overview
I'm trying to set up my PS1
prompt variable to dynamically choose a color. To do this, I've defined a bunch of local variables with color names:
$ echo $Green
\033[0;32m
but I was hoping to use those in dynamically assigning variables, but I can't figure out how to expand them properly:
> colorstr="\${$color}"
> echo $colorstr
${Green}
I've tried a dozen combinations of eval
, echo
, and double-quotes, and none seem to work. The logical way (I thought) to expand the variable results in an error:
> colorstr="${$color}"
-bash: ${$color}: bad substitution
(for clarity I've used >
instead of $
for the prompt character, but I am using bash)
How can I expand that variable? i.e., somehow get the word "Green" to the value \033[0;32m
? And prefereably, have bash or the terminal parse that \033[0;32m
as the color green too.
EDIT: I was mis-using ${!x}
and eval echo $x
previously, so I've accepted those as solutions. For the (perhaps morbidly) curious, the functions and PS1
variable are on this gist: https://gist.github.com/4383597
Bash Solutions
Solution 1 - Bash
Using eval
is the classic solution, but bash
has a better (more easily controlled, less blunderbuss-like) solution:
${!colour}
The Bash (4.1) reference manual says:
> If the first character of parameter is an exclamation point (!), a level of variable indirection is introduced. Bash uses the value of the variable formed from the rest of parameter as the name of the variable; this variable is then expanded and that value is used in the rest of the substitution, rather than the value of parameter itself. This is known as indirect expansion.
For example:
$ Green=$'\033[32;m'
$ echo "$Green" | odx
0x0000: 1B 5B 33 32 3B 6D 0A .[32;m.
0x0007:
$ colour=Green
$ echo $colour
Green
$ echo ${!colour} | odx
0x0000: 1B 5B 33 32 3B 6D 0A .[32;m.
0x0007:
$
(The odx
command is very non-standard but simply dumps its data in a hex format with printable characters shown on the right. Since the plain echo
didn't show anything and I needed to see what was being echoed, I used an old friend I wrote about 24 years ago.)
Solution 2 - Bash
Using eval should do it:
green="\033[0;32m"
colorstr="green"
eval echo -e "\$$colorstr" test # -e = enable backslash escapes
test
The last test is in color green.
Solution 3 - Bash
Bash supports associative arrays. Don't use indirection when you could use a dict. If you don't have associative arrays, upgrade to bash 4, ksh93, or zsh. Apparently mksh is adding them eventually as well, so there should be plenty of choice.
function colorSet {
typeset -a \
clrs=(black red green orange blue magenta cyan grey darkgrey ltred ltgreen yellow ltblue ltmagenta ltcyan white) \
msc=(sgr0 bold dim smul blink rev invis)
typeset x
while ! ${2:+false}; do
case ${1#--} in
setaf|setab)
for x in "${!clrs[@]}"; do
eval "$2"'[${clrs[x]}]=$(tput "${1#--}" "$x")'
done
;;
misc)
for x in "${msc[@]}"; do
eval "$2"'[$x]=$(tput "$x")'
done
;;
*)
return 1
esac
shift 2
done
}
function main {
typeset -A fgColors bgColors miscEscapes
if colorSet --setaf fgColors --setab bgColors --misc miscEscapes; then
if [[ -n ${1:+${fgColors[$1]:+_}} ]]; then
printf '%s%s%s\n' "${fgColors[${1}]}" "this text is ${1}" "${miscEscapes[sgr0]}"
else
printf '%s, %s\n' "${1:-Empty}" 'no such color.' >&2
return 1
fi
else
echo 'Failed setting color arrays.' >&2
return 1
fi
}
main "$@"
Though we're using eval
, it's a different type of indirection for a different reason. Note how all the necessary guarantees are made for making this safe.
See also: http://mywiki.wooledge.org/BashFAQ/006
Solution 4 - Bash
You will want to write an alias to a function. Check out http://tldp.org/LDP/abs/html/functions.html, decent little tutorial and some examples.
EDIT: Sorry, looks like I misunderstood the issue. First it looks like your using the variables wrong, check out http://www.thegeekstuff.com/2010/07/bash-string-manipulation/. Also, what is invoking this script? Are you adding this to the .bash_profile or is this a script your users can launch? Using export should make the changes take effect right away without needed relog.
var Green="\[\e[32m\]"
var Red="\[\e41m\]"
export PS1="${Green} welcome ${Red} user>"
Solution 5 - Bash
Your first result shows the problem:
> $ echo $Green \033[0;32m
The variable Green contains an string of a backlash, a zero, a 3, etc.
.
It was set by: Green="\033[0;32m"
. As such it is not a color code.
The text inside the variable needs to be interpreted (using echo -e, printf or $'...').
Let me explain with code:
$ Green="\033[0;32m" ; echo " $Green test "
\033[0;32m test
What you mean to do is:
$ Green="$(echo -e "\033[0;32m" )" ; echo " $Green test "
test
In great color green. This could print the color but will not be useful for PS1:
$ Green="\033[0;32m" ; echo -e " $Green test "
test
As it means that the string has to be interpreted by echo -e
before it works.
An easier way (in bash) is :
$ Green=$'\033[0;32m' ; echo " $Green test "
test
Please note the ` $'...' `
Having solved the issue of the variable Green
, accesing it indirectly by the value of var colorstr is a second problem that could be solved by either:
$ eval echo \$$colorstr testing colors
testing colors
$ echo ${!colorstr} testing colors
testing colors
Note Please do not work with un-quoted values (as I did here because the values were under my control) in general. Learn to quote correctly, like:
$ eval echo \"\$$colorstr testing colors\"
And with that, you could write an PS1 equivalent to:
export PS1="${Green} welcome ${Red} user>"
with:
Green=$'\033[0;32m' Red=$'\033[0;31m'
color1=Green color2=Red
export PS1="${!color1} welcome ${!color2} user>"