TransWikia.com

Command substitution in quotes cannot be used to call the variable

Unix & Linux Asked by Swaroop Joshi on November 28, 2021

Say:

variable="Something that it holds"

then
echo "$variable" will output: Something that it holds

But say I also do:

var2="variable";  
echo "$$(echo $var2)"

will just output: $variable
And not: Something that it holds

Can anyone tell me about what feature of Unix is in play, here?

4 Answers

In addition to indirect variable expansion, in bash (starting from version 4.3) you can use a nameref

declare -n var2="variable"  # "var2" is a _reference_ to "variable"

variable="Something"
echo "$var2"     # => Something

variable="something else"
echo "$var2"     # => something else

unset variable
echo "$var2"     # => ""

I don't know how this is implemented, but this is an interesting tidbit: you can find out what a nameref refers to using indirection:

echo ${!var2}    # => variable

Answered by glenn jackman on November 28, 2021

What you are observing is the standard behavior of a POSIX shell: in general, it

  1. reads its input;

  2. breaks the input into tokens: words and operators (token recognition);

    • during this step, any time it encounters an unquoted $ (or `), it recursively determines the type of expansion and the token to be expanded, reading all the needed input;
  3. parses the input into simple commands and compound commands;

  4. performs various expansions (separately) on different parts of each command, resulting in a list of pathnames and fields to be treated as a command and arguments;

  5. performs redirection and removes redirection operators and their operands from the parameter list;

  6. executes a function, built-in, executable file, or script;

  7. optionally waits for the command to complete and collects the exit status.

When parsing echo "$$(echo $var2)", the shell detects two expansions (step 2): the double-quoted command substitution $(echo $var2) and the unquoted parameter expansion $var2. The escaped $ in $ is taken as a literal dollar sign because a double-quoted retains its role as an escape character when followed by $.

No further detection of expansions happens at later stages. Specifically, there is no further parsing of the result of expansions performed in step 4 ("$$(echo $var2)""$$(echo variable)""$variable"$variable) that could detect expansion-triggering characters.

Also note that, while the $ symbol is used to replace the name of a variable with its content in the context of parameter expansion, it has not been designed as a general dereference operator.

In standard parameter expansion, whose simplest form is ${parameter}, the parameter specification is only allowed to be a variable name, a positional parameter or a special parameter (see the definition of "parameter"). Strictly speaking, parameter expansions can not be nested (an expansion expression is only allowed as word in the various ${parameter<symbols>[word]} forms).

You can easily verify that, with the exception of the Z shell, ${${foo}} is not a valid expression and that $$ expands to the shell's PID (thus, $foo expands to the value of foo, $$foo expands to the concatenation of the shell's PID and the literal "foo", $$$foo expands to the concatenation of the shell's PID and the value of foo, ...).

Answered by fra-san on November 28, 2021

variable="Something"
var2="variable";
echo "$$(echo $var2)"

In the last line, you expect "$$(echo $var2)"$variableSomething. This would require the shell to perform two parameter expansions. It does not, but rather simply prints the result of the command substitution $(echo $var2) prepended by a $.

In principle, eval helps you to get what you want. After the shell performs the first step, "$$(echo $var2)"$variable, eval performs the second step, $variableSomething.

$ eval echo "$$(echo $var2)"
Something

Although in our particular case the above command is OK, that still lacks a correct quoting, and printf is to be favored over echo,

$ eval 'printf "%sn" "${'"$var2"'}"'
Something

However, eval raises security concerns with untrusted data. Suppose var2="variable;rm importantFile". In that case, eval passes

echo $variable;rm importantFile

to the shell, which happily removes importantFile, if it exists.

In some shells (e.g.: Bash, ksh, Zsh) you can also do it using indirection. The syntax of indirect expansion in Bash is:

$ echo "${!var2}"
Something

var2="variable;rm importantFile" is not a problem anymore, but var2='a[$(rm importantFile)]' still is.

Read more about indirection in the Bash manual.

If the first character of parameter is an exclamation point (!), and parameter is not a nameref, it introduces a level of indirection. Bash uses the value formed by expanding the rest of parameter as the new parameter; this is then expanded and that value is used in the rest of the expansion, rather than the expansion of the original parameter

Answered by Quasímodo on November 28, 2021

Smells like an anti-pattern(*). Many shells have string-indexed arrays/dictionaries. In ksh93 (where that syntax comes from), bash or zsh:

typeset -A dictionary
x="keyX"
y="keyY"
dictionary[keyX]="valueX"
dictionary[$y]="valueY"

printf '%sn' "${dictionary[$x]}"
printf '%sn' "${dictionary[keyY]}"

(*) Nothing to do with Linux per se. The variable-name-in-a-variable is a very general "anti-pattern", something that shouldn't be done, and if you think you need it, there is a problem with your design (XY problem)? Most uses of a variable-name-in-a-variable are better replaced with a dictionary when that exists.

Answered by xenoid on November 28, 2021

Add your own answers!

Ask a Question

Get help from others!

© 2024 TransWikia.com. All rights reserved. Sites we Love: PCI Database, UKBizDB, Menu Kuliner, Sharing RPP