什么是间接膨胀?${!var*}的意思是什么?

115
我正在阅读《初学者 Bash 指南》。书中说:
如果 PARAMETER 的第一个字符是感叹号,Bash 将使用由剩余部分组成的变量作为变量名;然后展开该变量,并在替换的其余部分中使用该值,而不是使用 PARAMETER 本身的值。这被称为间接扩展。
书中给出的例子是:
franky ~> echo ${!N*}
NNTPPORT NNTPSERVER NPX_PLUGIN_PATH
我不太理解这里的意思:
PARAMETER 剩下的部分形成的变量值
由于 PARAMETER 只是 !N*,那么 PARAMETER 剩下的部分
只是 N*。那么这怎么能形成一个变量呢?是 Bash 在剩下的部分中搜索了所有可能的命令吗?
6个回答

142

如果您阅读了 bash 的 man 手册,它基本上确认了您所说的:

如果参数的第一个字符是感叹号 (!),则会引入一个变量间接级别。Bash 使用由参数的其余部分组成的变量值作为变量的名称;然后扩展该变量,并在替换的其余部分中使用该值,而不是参数本身的值。这称为间接扩展。

然而,继续往下读:

以下是描述 ${!prefix*}${!name[@]} 扩展的例外情况。

${!prefix*} 匹配前缀的名称。扩展为变量名以 prefix 开头的变量名,由 IFS 特殊变量的第一个字符分隔。

换句话说,您特定的例子 ${!N*} 是您引用的规则的一个例外情况。但是,在预期的情况下,例如:

$ export xyzzy=plugh ; export plugh=cave

$ echo ${xyzzy}  # normal, xyzzy to plugh
plugh

$ echo ${!xyzzy} # indirection, xyzzy to plugh to cave
cave

4
谢谢您的回答。我阅读《Bash初学者指南》时,越来越觉得作者是否真正理解自己所写的内容。 - LRDPRDX

31

当给定的“间接引用”以 * 结尾时(如此处所示),似乎会出现异常。在这种情况下,它会给出所有以指定部分开头的变量名(在此处为N)。Bash 之所以能够做到这一点,是因为它跟踪变量并知道哪些变量存在。

真正的间接引用是这样的:
假设我有一个变量 $VARIABLE 的值为 42,另外还有一个变量 $NAME 的值为 VARIABLE,那么 ${!NAME} 将给出 42。你使用一个变量的值来告诉你另一个变量的名称:

$ NAME="VARIABLE"
$ VARIABLE=42
$ echo ${!NAME}
42

16
bash的间接和/或nameref 对于这个问题回答晚了,而且因为没有其他回答提到nameref...
使用${!var} 间接语法:
~$ someVariable='Some content'
~$ var=someVariable

~$ echo $var
someVariable

~$ echo ${!var}
Some content

使用 nameref (declare -n) 语法

通过使用 nameref,你不仅可以显示变量的内容,还可以填充变量并获取或设置属性。

~$ someVariable='Some content'
~$ declare -n var=someVariable
~$ echo $var
Some content
~$ echo ${var@A}
someVariable='Some content'

这个语法对于函数很有用:

function showVarDetail() {
    local -n var=$1
    printf 'Variable \47\44%s\47 is %d len, has [%s] flags and contain: %q\n' \
        "$1" "${#var}" "${var@a}" "$var"
}

(注意:此函数仅为示例。它无法正确展开数组关联数组!)

然后

~$ someVar='Hello world!'
~$ showVarDetail someVar
Variable '$someVar' is 12 len, has [] flags and contain: Hello\ world\!

~$ declare -r PI=3.14159265358979323844
~$ showVarDetail PI
Variable '$PI' is 22 len, has [r] flags and contain: 3.14159265358979323844

使用nameref填充变量值

这可以以两种方式工作!

这里有一个小示例函数,使用两个变量名作为参数进行运行。第一个变量应包含一个字符串,而第二个变量将由第一个变量内容的第一个字符填充,然后将第一个变量内容向右移动 1 个字符:

shift1char <variable string source> <variable target>
shift1char () { 
    local -n srcStr=$1 tgtVar=$2;
    tgtVar=${srcStr::1} srcStr=${srcStr:1}
}

那么

~$ someVar='Hello world!'

~$ shift1char someVar someChar

~$ showVarDetail someVar
Variable '$someVar' is 11 len, has [] flags and contain: ello\ world\!

~$ showVarDetail someChar
Variable '$someChar' is 1 len, has [] flags and contain: H

通过一些小修改:

showVarDetail() { 
    local _nam
    for _nam in "$@"; do
        local -n _var=$_nam
        printf \
             'Variable \47\44%s\47 is %d len, has [%s] flags and contain: %q\n' \
             "${_nam}" "${#_var}" "${_var@a}" "$_var"
    done
}
move1char() {
    local -n srcStr=$1 tgtVar=$2
    [[ -z $srcStr ]] && return 1
    tgtVar+=${srcStr::1} srcStr=${srcStr:1}
}
someVar='Hello world!' target=''
while move1char someVar target;do
    showVarDetail someVar target
done

应该产生:

Variable '$someVar' is 11 len, has [] flags and contain: ello\ world\!
Variable '$target' is 1 len, has [] flags and contain: H
Variable '$someVar' is 10 len, has [] flags and contain: llo\ world\!
Variable '$target' is 2 len, has [] flags and contain: He
Variable '$someVar' is 9 len, has [] flags and contain: lo\ world\!
Variable '$target' is 3 len, has [] flags and contain: Hel
Variable '$someVar' is 8 len, has [] flags and contain: o\ world\!
Variable '$target' is 4 len, has [] flags and contain: Hell
Variable '$someVar' is 7 len, has [] flags and contain: \ world\!
Variable '$target' is 5 len, has [] flags and contain: Hello
Variable '$someVar' is 6 len, has [] flags and contain: world\!
Variable '$target' is 6 len, has [] flags and contain: Hello\ 
Variable '$someVar' is 5 len, has [] flags and contain: orld\!
Variable '$target' is 7 len, has [] flags and contain: Hello\ w
Variable '$someVar' is 4 len, has [] flags and contain: rld\!
Variable '$target' is 8 len, has [] flags and contain: Hello\ wo
Variable '$someVar' is 3 len, has [] flags and contain: ld\!
Variable '$target' is 9 len, has [] flags and contain: Hello\ wor
Variable '$someVar' is 2 len, has [] flags and contain: d\!
Variable '$target' is 10 len, has [] flags and contain: Hello\ worl
Variable '$someVar' is 1 len, has [] flags and contain: \!
Variable '$target' is 11 len, has [] flags and contain: Hello\ world
Variable '$someVar' is 0 len, has [] flags and contain: ''
Variable '$target' is 12 len, has [] flags and contain: Hello\ world\!

3

是的,它会在!后搜索所有可能的变量扩展。如果你这样做:

echo ${!NP*}

您只会得到NPX_PLUGIN_PATH

考虑以下示例:

:~> export myVar="hi"
:~> echo ${!my*}
    myVar
:~> export ${!my*}="bye"
:~> echo $myVar
    bye

与“my*”匹配的其他变量也会被设置为“bye”吗? - Anthony
1
@Anthony 我尝试了一下,如果 ${!my*} 扩展为 myA,myB,那么 myA 将以其当前值导出,而 myB 则设置为 "bye" 并导出。并不是很有用。 - GKFX

3

在间接处理中,您遇到了一个异常情况,如果最后一个字符是*,则将返回所有具有给定前缀的变量。


除了 * 的情况外,这与 ${${VAR}} 是一样的吗? - chronospoon
1
@chronospoon, ${${VAR}},更简洁地写作${$VAR},是不合法的,因为$VAR返回一个字符串,不能跟随$符号;要使用字符串作为变量名,您需要引入一级间接性(如在原始问题中引用),即您可以使用${!VAR},它正好执行您期望(错误但可以理解)${$VAR}的操作。 - Enlico

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接