Bash expand variable in a variable

Bash

Bash 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>"

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkeflavichView Question on Stackoverflow
Solution 1 - BashJonathan LefflerView Answer on Stackoverflow
Solution 2 - BashperrealView Answer on Stackoverflow
Solution 3 - BashormaajView Answer on Stackoverflow
Solution 4 - Bashuser1931103View Answer on Stackoverflow
Solution 5 - Bashuser2350426View Answer on Stackoverflow