什么是间接扩张? ${ ! var * }是什么意思?

我在读“ 初学者指南”上面写着:

如果 PARAMETER的第一个字符是一个叹号,那么 Bash 使用由其余的 PARAMETER形成的变量的值作为变量的名称,然后展开这个变量,在其余的替换中使用这个值,而不是使用 PARAMETER本身的值。这就是所谓的间接扩张。

给出的例子是:

franky ~> echo ${!N*}
NNTPPORT NNTPSERVER NPX_PLUGIN_PATH

我不太明白:

PARAMETER的其余部分形成的变量的值

因为 PARAMETER就是 !N*

PARAMETER的其余部分

这是如何形成一个变量? Bash 搜索了所有可能的命令吗?

58647 次浏览

Yes, it searches for all possible expansions of variables after the !. If you had done:

echo ${!NP*}

you would get only NPX_PLUGIN_PATH.

Consider the following example:

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

You've hit an exception in indirection processing, where if the last character is *, all variables that have the prefix given before will be returned.

If you read the bash man page, it basically confirms what you have stated:

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.

However, reading on from there:

The exceptions to this are the expansions of ${!prefix*} and ${!name[@]} described below.

${!prefix*} Names matching prefix. Expands to the names of variables whose names begin with prefix, separated by the first character of the IFS special variable.

In other words, your particular example ${!N*} is an exception to the rule you quoted. It does, however, work as advertised in the expected cases, such as:

$ export xyzzy=plugh ; export plugh=cave


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


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

There appears to be an exception when the given "indirection" ends in a *, as it does here. In this case, it gives all variable names that start with the part you specified (N here). Bash can do that because it tracks variables and knows which ones exist.

True indirection is this:
Say I have a variable $VARIABLE set to 42, and I have another variable $NAME set to VARIABLE. ${!NAME} will give me 42. You use the value of one variable to tell you the name of another:

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

You can refer to this GNU doc for bash for authoritative information

https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#Shell-Parameter-Expansion

But basically, indirect expansion is not performed on ${!prefix*} as one of the exceptions, in your example, N is the prefix.

The Document will explain what indirect expansion is in bash

bash indirection and/or nameref

Comming late on this question, and because no other answer tell about nameref...

Using ${!var} indirection syntax:

~$ someVariable='Some content'
~$ var=someVariable


~$ echo $var
someVariable


~$ echo ${!var}
Some content

Using namref (declare -n) syntax

By using nameref you could not only show content of variable, but you can populate variable and get or set attributes.

~$ someVariable='Some content'
~$ declare -n var=someVariable
~$ echo $var
Some content

This syntax is usefull for functions:

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

(Nota: This function is only a sample. This won't expand correctly arrays and associative arrays!)

Then

~$ 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

Populating variable values using nameref

This could work in both ways!

Here is a little sample function to run with two variable names as arguments. First variable should contain a string and second variable will be populated by 1st character of 1st variable content, then 1st variable content will be shifted by 1 character:

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

Then

~$ 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