变量中的 Bash 展开变量

我试图设置我的 PS1提示变量来动态选择一种颜色。为此,我定义了一组带有颜色名称的局部变量:

$ echo $Green
\033[0;32m

但是我希望用它们来动态分配变量,但是我不知道如何正确地展开它们:

> colorstr="\${$color}"
> echo $colorstr
${Green}

我已经尝试了十几种 evalecho和双引号的组合,但似乎没有一种奏效。我认为展开变量的逻辑方法会导致一个错误:

> colorstr="${$color}"
-bash: ${$color}: bad substitution

(为了清楚起见,我在提示字符中使用了 >而不是 $,但是我使用的是 bash)

如何展开这个变量?也就是说,用某种方法将“绿色”这个单词的值转换成 \033[0;32m?并且最好有 bash 或者 \033[0;32m作为颜色绿色的终端解析器。

编辑: 我以前错误地使用了 ${!x}eval echo $x,所以我接受它们作为解决方案。对于(可能是病态的)好奇,函数和 PS1变量在这个要点: https://gist.github.com/4383597

88449 次浏览

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

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.

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.)

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

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