0

Ubuntu 22.04.2 LTS GNU bash, version 5.1.16(1)-release (x86_64-pc-linux-gnu)

My Task

I want to test in a generic way if var is set and not empty.

The script

#!/bin/bash

set -e
set -u
set -o pipefail

if [ $# -ne 1 ] ; then
    echo "num: $#"
    echo "Usage: $0 <VAR_NAME>"
    exit 1
fi
varname=$1

cmd="if [ -z \"\$$varname\" ]; then echo \"$varname is blank or unset\"; else echo \"$varname is set to '\$$varname'\"; fi"

echo "$cmd"
eval "$cmd"

this script a based on that site:

How to check if a variable is set in Bash

The actual behaviour 1 (it does not work)

if you execute your statement like

bla=3
./trv_test_var.sh "bla"

you receive the following response:

if [ -z "$bla}" ]; then echo "bla is blank or unset"; else echo "bla is set to '$bla'"; fi
./trv_test_var.sh: line 19: bla: unbound variable

but if you exceute the outputed command it works as expected:

if [ -z "$bla" ]; then echo "bla is blank or unset"; else echo "bla is set to '$bla'"; fi
bla is set to '3'

The actual behaviour 2 (it works but wrongly)

First of all please change the line 14 with the followin code:

cmd="if [ -z \${$varname+x} ]; then echo \"$varname is unset\"; else echo \"$varname is set to '\$$varname'\"; fi"

Now:

bla=3
./trv_test_var.sh "bla"

you receive:

if [ -z ${bla+x} ]; then echo "bla is blank or unset"; else echo "bla is set to '$bla'"; fi
bla is blank or unset

but if you exceute the outputed command it works as expected:

if [ -z ${bla+x} ]; then echo "bla is blank or unset"; else echo "bla is set to '$bla'"; fi
bla is set to '3'

The desired behaviour:

I execute the statement with

cmd="if [ -z \"\$$varname\" ]; then echo \"$varname is blank or unset\"; else echo \"$varname is set to '\$$varname'\"; fi"

and it shows me if the var set or not.

Please help.

Biffen
  • 6,249
  • 6
  • 28
  • 36
paulh00
  • 9
  • 2
  • 5
    Why do you want to store the command in a variable at all? Don't do that. See https://mywiki.wooledge.org/BashFAQ/050 – tripleee May 05 '23 at 12:05
  • yeah, just put the command text in the script instead of `eval`. Avoid `eval`. – erik258 May 05 '23 at 12:07
  • Does this a̶n̶s̶w̶e̶r̶ ̶y̶o̶u̶r̶ ̶q̶u̶e̶s̶t̶i̶o̶n̶ _help_? [Dynamic variable names in Bash](https://stackoverflow.com/questions/16553089/dynamic-variable-names-in-bash) – Biffen May 05 '23 at 12:29
  • Why do you use `eval`? What cannot be done without it, for what you need? – axiac May 05 '23 at 12:39
  • See [Why should eval be avoided in Bash, and what should I use instead?](https://stackoverflow.com/q/17529220/4154375). – pjh May 05 '23 at 13:09
  • `bla=3; ./trv_test_var.sh "bla"` doesn't work because the `bla` variable is not visible to the `trv_test_var.sh` program. See [When running a bash script within another script, are all variables defined in the parent script inherited to the child?](https://stackoverflow.com/q/43606290/4154375). – pjh May 05 '23 at 13:16
  • The `unbound variable` error occurs because of `set -u` (equivalent to `set -o nounset`). There are many difficulties associated with using `set -u`, `set -e` (particularly), and `set -o pipefail`. See [Bash Pitfalls #60 (set -euo pipefail)](https://mywiki.wooledge.org/BashPitfalls#set_-euo_pipefail) and [BashFAQ/105 (Why doesn't set -e (or set -o errexit, or trap ERR) do what I expected?)](https://mywiki.wooledge.org/BashFAQ/105). – pjh May 05 '23 at 13:37

1 Answers1

0

Your approach is difficult. eval is not needed and not that easy to make it work.

But there is a better and much simpler solution. It relies on the indirection functionality offered by Bash in parameter expansion. The manual says:

The basic form of parameter expansion is ${parameter}. The value of parameter is substituted. ... 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. This is known as indirect expansion.

Your code can be as simple as:

isset() {
  local varname=$1

  if [ -n "${!varname}" ]; then
    echo "Variable '$varname' is set"
  else
    echo "Variable '$varname' is empty or not set"
  fi
}

FOO=bar

isset FOO
isset BAR

You can write a function or a script, the code is the same (the body of the function listed above).

Check it online.

Update

It can be done even more simple, without indirection. Use -v and do not expand the variable that you want to check. Expand only the parameter to find the name of the variable to check:

isset() {
  local varname=$1

  if [ -v "$varname" ]; then
    echo "Variable '$varname' is set"
  else
    echo "Variable '$varname' is empty or not set"
  fi
}

FOO=bar

isset FOO
isset BAR

Check it online.

axiac
  • 68,258
  • 9
  • 99
  • 134
  • Yes, ^This. Don't use eval. Indirection or `-v` are much better. – Paul Hodges May 05 '23 at 13:42
  • The function that uses `[ -n "${!varname}" ]` is vulnerable to code injection (`isset 'a[$(rm /)]'`) and a variable name clash (`unset varname; isset varname`). It also breaks for unset variables if `nounset` is on (`( set -o nounset; unset var; isset var )`). The function that uses `[ -v "$varname" ]` is also vulnerable to code injection and a variable name clash, though it's OK with `nounset`. In addition, it doesn't handle a set but empty variable correctly (`var=; isset var`). It also doesn't work with versions of Bash older than 4.2 (e.g. the default Bash on macOS). – pjh May 05 '23 at 23:01
  • according to stackoverflow.com/questions/17529220/… I only need the command: if [ -z "${!varname}" ]; then echo "$varname is blank or unset"; else echo "$varname is set to '${!varname}'"; fi. – paulh00 May 09 '23 at 06:29
  • `-z` and `-n` are opposite. One returns `true` when the argument is empty, the other when it is not empty. – axiac May 09 '23 at 06:58
  • Don't rely on StackOverflow answers. Many of them are incorrect or have hidden bugs, including this one. – axiac May 09 '23 at 06:59