124

I'm reading "Bash Guide for Beginners". It says:

If the first character of PARAMETER is an exclamation point, 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.

The example given is:

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

I don't quite understand here:

the value of the variable formed from the rest of PARAMETER

As the PARAMETER is just !N*, then

the rest of PARAMETER

is just N*. How could this form a variable? Did Bash search all possible commands there?

2
  • This is one of the very very few examples of a bash feature that isn't emulated in zsh. Commented May 21, 2024 at 21:54
  • @SridharSarnobat : Why are you mentioning zsh? There is nothing about zsh in the question? Commented Nov 27, 2024 at 9:53

6 Answers 6

152

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

An example of ${!prefix*}:

$ xyzzy=plugh
$ xyzzz=flume

$ echo ${!xyzz*}
xyzzy xyzzz
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the answer. The more I read "Bash guide for Beginners" the more I ask myself whether the author understands what she writes.
@LRDPRDX, is that guide from TLDP? If so, avoiding what they write is generally for the better; the "advanced" bash guide from there is certainly frowned on.
32

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

Comments

24

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
~$ echo ${var@A}
someVariable='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

~$ declare -ir answerOfUltimateQestionOfLiveUniverseAndEverything=42
~$ showVarDetail answerOfUltimateQestionOfLiveUniverseAndEverything
Variable '$answerOfUltimateQestionOfLiveUniverseAndEverything' is 2 len, has [ir
] flags and contain: 42

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

With some little modifications:

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

should produce:

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\!

Modifying variable content by conversion

Here is a little function to convert elapsed time from ps command's output into seconds:

etime2sec() { 
    local -n _etim=$1;
    local _sec=${_etim##*:} _min;
    _min=0${_etim/%$_sec};
    _min=${_min%:};
    _min=${_min/[^0-9:]/*1440+10#};
    _min=${_min/:/*60+10#};
    _etim=$(((10#$_min)*60+10#$_sec))
}

Then

while read pid tty etime cmd ;do
    etime2sec etime
    printf '%8d %-7s %11d %(%a %d %T)T %s\n' \
        "$pid" "$tty" "$etime" $((EPOCHSECONDS-etime)) "$cmd"
done < <(
    ps -C bash wwho pid,tty,etime,cmd
) |
    sort -rnk 3

Will show all your currently running bash process, sorted by age.

    6134 pts/0       2732427 Tue 11 19:39:35 bash
    7626 pts/1       2732279 Tue 11 19:42:03 /bin/bash
    8048 pts/2       2732262 Tue 11 19:42:20 /bin/bash
    8400 pts/3       2732244 Tue 11 19:42:38 /bin/bash
 3254682 pts/5          7175 Sat 15 08:40:27 bash
 3986601 pts/5             0 Sat 15 10:40:02 bash

Comments

4

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

2 Comments

would other variables that match my* also be set to "bye"?
@Anthony I tried it, and if ${!my*} expands to myA, myB, myA is exported with its current value, and myB is set to "bye" and exported. Not very useful.
3

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.

2 Comments

So aside from the * case, is this the same as ${${VAR}}?
@chronospoon, ${${VAR}}, more shortly writable as ${$VAR}, is not legal, since $VAR returns a string, which can't follow the $ sign; to use a string as a variable name you need to introduce one level of indirection (as quoted in the original question itself), i.e. you can use ${!VAR}, which does exactly what you would expect (erroneously but understandably) ${$VAR} does.
0

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.