5

As explained in this answer, the "right" way to check if a variable is set in bash looks like this:

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

What I'm interested in is how to extract this into a function that can be reused for different variables.

The best I've been able to do so far is:

is_set() {
  local test_start='[ ! -z ${'
  local test_end='+x} ]'
  local tester=$test_start$1$test_end

  eval $tester
}

It seems to work, but is there a better way that doesn't resort to calling eval?

5
  • Making a function to test if a variable is set doesn't seem like a good idea. You will either be hardcoding the variable you want to check (which makes no sense), using eval not recommended, especially when you cannot be absolutely assured of the variable content, or passing the value of the variable to check as a parameter which takes you back to a [[ -z $1 ]] test to begin with. Commented May 27, 2017 at 20:09
  • 5
    There is a subtle distinction here: [[ -z $var ]] returns true if var is either an empty string or unset. [[ -z ${var+x} ]] tests strictly for unset. Commented May 27, 2017 at 20:11
  • 1
    @John1024: good catch! Commented May 27, 2017 at 20:19
  • @DavidC.Rankin I wanted to write a function to make the code self-documenting, since the syntax is pretty cryptic. Given the complications though, I agree with you, and it got me thinking about the difference between documenting the domain logic you're composing and documenting the workings of the language itself. Since the language is resisting my efforts here, I'll stick with what's simplest, and not try to wrap it in a function. Commented May 28, 2017 at 4:22
  • I'm not 100% certain you will get the same results in bash 3, but you can indirectly check a variable using the ! operator isunset() { [[ -z ${!1+x} ]];} You can then use if isunset var; then echo var is not set Commented May 28, 2017 at 15:21

4 Answers 4

6

In Bash you can use [[ -v var ]]. There's no need for a function or convoluted schemes.

From the manpage:

   -v varname
          True if the shell variable varname is set (has been assigned a value).

The first 2 command sequences prints ok:

[[ -v PATH ]] && echo ok

var="" ; [[ -v var ]] && echo ok

unset var ; [[ -v var ]] && echo ok
Sign up to request clarification or add additional context in comments.

3 Comments

I'm guessing this was added in a newer version of Bash than what I'm running? I get bash: conditional binary operator expected, but I'm using the version of Bash that came with OSX (3.2.57). I should probably upgrade anyway, but I'm writing this script to help coworkers set up part of their dev environment, so portability is a concern.
Then a cleaner way like [[ -z ${var+x} ]], as was pointed out earlier, it's the way to go. Does it work in Bash 3? Running eval in a function for a simple check is ugly and asking for trouble.
Yes, the [[ -z ${var+x} ]] approach works in Bash 3. I'll stick with that. It's good to know that -v was added though, thanks!
2

With [[ ... ]], you can simply do this (there is no need for double quotes):

[[ $var ]] && echo "var is set"
[[ $var ]] || echo "var is not set or it holds an empty string"

If you really want a function, then we can write one-liners:

is_empty()     { ! [[ $1 ]]; } # check if not set or set to empty string
is_not_empty() {   [[ $1 ]]; } # check if set to a non-empty string

var="apple"; is_not_empty "$var" && echo "var is not empty" # shows "var is not empty"
var=""     ; is_empty "$var"     && echo "var is empty"     # shows "var is empty"
unset var  ; is_empty "$var"     && echo "var is empty"     # shows "var is empty"
var="apple"; is_empty "$var"     || echo "var is not empty" # shows "var is not empty"

Finally, is_unset and is_set could be implemented to treat $1 as the name of the variable:

is_unset() {   [[ -z "${!1+x}" ]]; }           # use indirection to inspect variable passed through $1 is unset
is_set()   { ! [[ -z "${!1+x}" ]]; }           # check if set, even to an empty string

unset var; is_unset var && echo "var is unset" # shows "var is unset"
var=""   ; is_set var   && echo "var is set"   # shows "var is set"
var="OK" ; is_set var   && echo "var is set"   # shows "var is set"

Related

Comments

0

Bash 4.2+

if [[ -v $MY_VARIABLE ]]; then
    echo "MY_VARIABLE is set"
fi

Older Versions(MacOS)

if [[ -n ${MY_VARIABLE+set} ]]; then
    echo "MY_VARIABLE is set"
else

Comments

0

while [[ -v $varname ]] satisfies the need to know if a variable exists - you could take this an overkilling step further and determine what type a variable has if it exists (including empty hashmaps, arrays with a single nullstring, etc)

# @usage: typeof <varname>|<command> [<matcher>] [<match>]
#         if matchers are used no output is generated
# @return: array|boolean|builtin|file|function|number|object|string|undefined
typeof(){
  local name="$1" data="${!1}" type=string
  if [[ "$name" =~ ^[_[:alpha:]][_[:alpha:][:digit:]]*$ ]]; then
    declare -a attr; read -ra attr < <( declare -p "$name" 2>/dev/null )
    case "${attr[1]}" in
      '')  type="$( type -t "$name" 2>/dev/null )" || type=undefined;;
      *A*) type=object; declare -n data="$name";;
      *a*) type=array; declare -n data="$name";;
      *i*) type=number;;
      *) if [[ "$data" == ?(-|+)+([0-9])?(.+([0-9])) ]]; then type=number
         elif [[ "$data" == @(true|false) ]]; then type=boolean; fi
    esac
  fi
  [ $# -lt 2 ] && echo $type || case "$2" in
    ===|==|=) [ "$type" == "$3" ] || return 2;;
    !=) [ "$type" != "$3" ] || return 2;;
    is) case "$3" in
        readonly) [[ "${attr[1]}" == *r* ]];;
        executable) [[ "$type" == @(builtin|file|function) ]];;
        empty) [ ${#data[@]} == 0 ];;
      esac ;;
    =~|matches) [[ "$data" =~ $3 ]];;
  esac
}

# examples
declare -a myarray && typeof myarray == array && echo "✓ array length=${#myarray[@]}"
declare -A mymap=([foo]=bar) && typeof mymap != object || echo "✓ it's a map" ;
echo -n "✓ undefined is " && typeof undefinedvariable
echo -n "✓ callable " && typeof typeof
echo -n "✓ command " && typeof ls
echo -n "✓ echo is " && typeof echo
declare -i num && typeof num == number && echo "✓ number ${num:-is empty}" ;
readonly one=1 && typeof one == number && typeof one is readonly && echo "✓ number one '$one' is readonly" ;
typeof pwd is executable && echo "✓ pwd is $_";

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.