12

I found a script that has a function that checks if a variable is set but i don't understand it very well.

check_if_variable_is_set() {
    var_name=$1
    if [ -z "${!var_name+x}" ]; then
        false
    else
        true
    fi
}

What exactly happens with this substitution ?

1 Answers1

19

In the bash shell, ${!var} is a variable indirection. It expands to the value of the variable whose name is kept in $var.

The variable expansion ${var+value} is a POSIX expansion that expands to value if the variable var is set (no matter if its value is empty or not).

Combining these, ${!var+x} would expand to x if the variable whose name is kept in $var is set.

Example:

$ foo=hello
$ var=foo
$ echo "${!var+$var is set, its value is ${!var}}"
foo is set, its value is hello
$ unset foo
$ echo "${!var+$var is set, its value is ${!var}}"

(empty line as output)


The function in the question could be shortened to

check_if_variable_is_set () { [ -n "${!1+x}" ]; }

or even:

check_if_variable_is_set () { [ -v "$1" ]; }

or even:

check_if_variable_is_set()[[ -v $1 ]]

Where -v is a bash test on a variable name which will be true if the named variable is set, and false otherwise.


POSIXly, it could be written:

check_if_variable_is_set() { eval '[ -n "${'"$1"'+x}" ]'; }

Note that all those are potential command injection vulnerabilities if the argument to that function could end up being under control of an attacker. Try for instance with check_if_variable_is_set 'a[$(id>&2)]'.

To guard against that, you may want to verify that the argument is a valid variable name first. For variables:

check_if_variable_is_set() {
  case $1 in
    ("" | *[![:alnum:]_]* | [![:alpha:]_]*) false;;
    (*)  eval '[ -n "${'"$1"'+x}" ]'
  esac
}

(note that [[:alpha:]] will check for alphabetical characters in your locale while some shells only accept alphabetical characters from the portable character set in their variable)

Kusalananda
  • 333,661