121

I need to check a variable's existence in an if statement. Something to the effect of:

if [ -v $somevar ]
then
    echo "Variable somevar exists!"
else
    echo "Variable somevar does not exist!"

And the closest question to that was this, which doesn't actually answer my question.

  • If you want to set $somevar to a value/string if variable does not exist: ${somevar:=42}. – Cyrus Jun 25 '15 at 18:49
  • Personally, I tend to check just for emptiness ([ -n "$var" ] or [ ! -z "$var" ]). I think the existence/nonexistence checks are too subtle, and I prefer my code coarse and simple. – Petr Skocik Dec 01 '15 at 17:12
  • Why do you need that vs [ -n "$var" ]? Related: http://stackoverflow.com/questions/3601515/how-to-check-if-a-variable-is-set-in-bash – Ciro Santilli OurBigBook.com Jun 28 '16 at 10:36

12 Answers12

153

In modern bash (version 4.2 and above):

[[ -v name_of_var ]]

From help test:

-v VAR, True if the shell variable VAR is set

Chris Down
  • 125,559
  • 25
  • 270
  • 266
46

Depends what you mean by exists.

Does a variable that has been declared but not assigned exist?

Does an array (or hash) variable that has been assigned an empty list exist?

Does a nameref variable pointing to a variable that currently isn't assigned exist?

Do you consider $-, $#, $1 variables? (POSIX doesn't).

In Bourne-like shells, the canonical way is:

if [ -n "${var+set}" ]; then
  echo '$var was set'
fi

That works for scalar variables and other parameters to tell if a variable has been assigned a value (empty or not, automatically, from the environment, assigments, read, for or other).

For shells that have a typeset or declare command, that would not report as set the variables that have been declared but not assigned (note that in zsh, declaring a variable assigns a value, a default one if not specified).

For shells that support arrays, except for yash and zsh that would not report as set array variables unless the element of indice 0 has been set.

For bash (but not ksh93 nor zsh), for variables of type associative array, that would not report them as set unless their element of key "0" has been set.

For ksh93 and bash, for variables of type nameref, that only returns true if the variable referenced by the nameref is itself considered set.

For ksh, zsh and bash, a potentially better approach could be:

if ((${#var[@]})); then
  echo '$var (or the variable it references for namerefs) or any of its elements for array/hashes has been set'
fi

For ksh93, zsh and bash 4.4 or above, there's also:

if typeset -p var 2> /dev/null | grep -q '^'; then
  echo '$var exists'
fi

Which will report variables that have been set or declared.

  • 2
    declare -p / typeset -p works in bash now too. – cas May 02 '16 at 20:12
  • 1
    @cas, no. Not for declared but not set variables. Try bash -c 'typeset -i a; typeset -p a' and compare with ksh93 or zsh. – Stéphane Chazelas May 02 '16 at 21:34
  • +1 Thanks for point me here. Also see my question http://unix.stackexchange.com/q/280893/674 – Tim May 03 '16 at 20:15
  • @cas, that changed with bash-4.4 (released in September 2016), I've edited that in. – Stéphane Chazelas Nov 02 '17 at 12:55
  • Don't check the length (the -n flag for test) in POSIX. Simply do [ "${var+.}" ]. The plus expansion substitutes unset to null string and test evaluates true if string is not the null string, thus the entire expression evaluates to false only when $var is unset. – antichris May 09 '20 at 22:11
  • 1
    @antichris, that's a matter of taste. I do prefer the [ -n string ] over [ string ] as it makes it clearer what is being tested as it follows the usual [ -operator operand ] pattern. – Stéphane Chazelas May 10 '20 at 06:53
  • @StéphaneChazelas No, you are mistaken. It is a matter of performance. Most of POSIX compliant implementations (zsh being an exception), return something along the lines of argv[pos][0] != '\0' immediately when a single argument expression is parsed. When the first argument of two starts with a - (as would be the case with -n) they descend into a more or less gnarly unary operator resolution. I really don't see any added value in typing longer expressions that evaluate (marginally, true, but still) slower, but have the exact same result (in this case). – antichris May 11 '20 at 09:39
  • 3
    @antichris, that's hardly relevant, in both cases it's a builtin call. You might save 10% of a few hundred nanosecond calls with some implementation when a shell's job is to run commands which take at least a few miliseconds to run. I don't want to be drawn into this kind of micro-optimisations when that diminishes legibility. [ -z ], [ -n ] come together for null and non-null string tests. I'm not going to replace [ -z "$var" ] with [ ! "$var" ] or [ "$#" -ne 0 ] with [ "${1+x}" ], we'll have to agree to disagree on our priorities. – Stéphane Chazelas May 11 '20 at 10:19
  • @StéphaneChazelas [ "${var+.}" ] is shorter, sweeter and faster (and those factors become significant, for example, when dropping a shellcode). Just saying. But you do you. – antichris May 12 '20 at 16:48
  • @antichris no it isn't for that vast majority. It's shorter and sweeter for experts that revel in such things, I do. But its not at all legible to anyone trying to understand a script when they are not experts. -n is easily googled and checked. Nobody cares about micro optimisations in a script that might be called once per session and, in any case, you could argue the compiler/interpreter would optimise it anyway. – RichieHH Jan 25 '21 at 10:55
  • @RichieHH you almost got it, it's just the other way around. When I first saw the [ -n "$var" ] idiom in a script, I did indeed have to look it up (google or man), because a flag like that could mean practically anything (especially to a non-expert, inexperienced first-timer, as I was back then), while the meaning of [ "$var" ] and [ ! "$var"] has always seemed perfectly obvious to me, right from the very beginning. It's precisely the -n and -z flags that I still have to look up every once in a while to verify that I'm not mistaking one for the other. – antichris Jan 25 '21 at 15:29
  • @antichris, and do you find it obvious that [ 0 ] or [ ! ] should return true? Why should [ x ] check for that very property that x be non-empty as opposed to any other property/test one could have chosen instead (is a non-zero number, is a readable file, is a know test operator...). Note that [ -t ] in some shells/tests returns true if stdin is a terminal. [[ $var ]] used not to be supported in zsh. – Stéphane Chazelas Jan 25 '21 at 15:39
  • @StéphaneChazelas Sure. The test command takes parameters, they have semantics and are very well documented. As for the non-emptiness check, in my experience, all languages that accept a single variable as a boolean expression have converged upon checking the "truthiness" of its value. Since POSIX shell variables can only contain (null-terminated) string values, it seems self-evident that a non-empty one must be truthy. POSIX spec has the FD number for a -t test mandatory, what you're describing is non-compliant. Double-brackets and zsh are non-POSIX so, frankly, IDGAF about either. – antichris Jan 26 '21 at 00:16
16

As mentioned in the answer on SO, here is a way to check:

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

where ${somevar+x} is a parameter expansion which evaluates to the null if var is unset and substitutes the string "x" otherwise.

Using -n, as suggested by the other answer, will only check if the variable contains empty string. It will not check its existence.

shivams
  • 4,565
  • 2
    You need to quote $somevar to handle IFS=x. Either that or quote x. – mikeserv Jun 26 '15 at 00:02
  • 1
    @mikeserv Thanks, I like learning about edge cases :) Do you mean if [ -z "${somevar+x}" ]? Would the quoting still be required inside [[ and ]]? – Tom Hale Sep 03 '18 at 14:19
  • @TomHale - yes, in rare cases. the [ test routines accept command line parameters, and so the usual expansions and interpretations as ordered in the usual way should be relied upon to render at invocation of the test applied that which you should cause to be read thereby by any program command line. test{!+"!"} – mikeserv Oct 10 '18 at 06:02
  • @mikeserv I guess your yes is to my 2nd question... Is the first a yes, too? – Tom Hale Oct 10 '18 at 14:14
  • @Tom Hale - to my my way of thinking, [ -z, if applied at all to a parameter check, is most useful as a corner case standin. the not applicable corollary... [ ${*?cant handle that, bash? or do the prior bournes slip at :? theres a test for that, too, if your word is worth the encoding... save for later? freezers are cool} and plus works for almost anything (stay positive, everybody) [ ${-+"-z"} $- ]&& now what? of course, by design, file system glo[bal]{1,2}ing might not be so confusing as a possible forty-two deep link back check... fortuitous? thats quite a lot. – mikeserv Oct 16 '18 at 19:23
  • Sorry @mikeserv, I have no idea what you mean... would you mind to try again with more simple examples? – Tom Hale Oct 17 '18 at 12:40
  • 2
    +1; this appears to be the simplest way to do this when set -u is in effect and the Bash version is pre-4.2. – Kyle Strand Jan 08 '19 at 22:56
  • @KyleStrand I usually do that with [[ -n ${MY_VAR:-} ]] – Jupiter Apr 06 '22 at 09:13
  • @Jupiter Yeah, that should be equivalent, I think, though it may need quotes around the expansion. – Kyle Strand Apr 06 '22 at 17:08
6

POSIXly:

! (: "${somevar?}") 2>/dev/null && echo somevar unset

or you can let your shell show the message for you:

(: "${somevar?}")
zsh: somevar: parameter not set
cuonglm
  • 153,898
  • @mikeserv: Yeah, of course, there's many way to do it. Firstly, I think I will duplicated it with this. But in this question, the OP want to check only, he didn't claim that he want to exit or report if variable unset, so I came with a check in subshell. – cuonglm Jun 26 '15 at 01:46
  • 3
    Well, i know, but it's precisely because you have to do it in a subshell like that which indicates it might not be the best way to test here - that's a halt action on failure, it doesn't allow for any simple means to handle a failure. Portably even a trap can only work on EXIT. That's all I'm saying - it just doesn't apply as a pass/fail very well. And this isn't me talking either - i've done exactly this before and it took a little comment chat just like this to convince me. So, I just thought I'd pay it forward. – mikeserv Jun 26 '15 at 01:51
  • 1
    @mikeserv: Well, well, it's a good point. I only wonder, does adding that can make the OP confuse with the syntax :) – cuonglm Jun 26 '15 at 02:24
  • you might want to "peel the onion" a bit here to unpack that command for the OP or other bash learners. It's definitely packing a lot of concepts, aside from the fact that it doesn't feature the "if statement" that the OP asked about. – init_js Feb 18 '23 at 04:18
3
if set|grep '^somevar=' >/dev/null;then
    echo "somevar exists"
else
    echo "does not exist"
fi
cuonglm
  • 153,898
Mel
  • 610
  • I would imagine this to be somewhat inefficient, but it is very simple and sh compatible, which is just what I need. – hoijui Aug 28 '19 at 08:27
3

This simple line works (and works on most POSIX shells):

${var+"false"} && echo "var is unset"

Or, written in a longer form:

unset var

if ${var+"false"}
then
   echo "var is unset"
fi

The expansion is:

  • If the var has a value (even null), false is replaced
  • If the var has "no value", then "no value" (null) is replaced.

The ${var+"false"} expansion expands to either "null" of "false".
Then, "nothing" or the "false" is executed, and the exit code set.

There is no need to call the command test ([ or [[) as the exit value is set by the (execution of) the expansion itself.

  • Yes, except in some old versions of zsh in sh emulation when $IFS contains f, a, l, s or e. Like for other answers, there's the case of arrays, hashes, or other types of variables which one may want to mention. – Stéphane Chazelas Dec 01 '15 at 21:00
  • 1
    @StéphaneChazelas I wrote most POSIX shells. most means In the greatest number of instances, not all. ... ... So, yes, in an obscure condition when $IFS contains f, a, l, s or e and for some obscure shell some old versions of zsh this fails: What a shock!. I should assume that such bug has been solved long ago. ... ... Are you proposing that we must write code for long ago broken shells?. –  May 30 '16 at 01:43
  • @StéphaneChazelas Also: The question title is very specific: Bash. –  May 30 '16 at 01:46
  • binary zebra??? – mikeserv Oct 16 '18 at 19:12
2
printf ${var+'$var exists!\n'}

...will print nothing at all when it doesn't. Or...

printf $"var does%${var+.}s exist%c\n" \ not !

...will tell you either way.

you can use the return value of a test to dynamically expand to the appropriate format string for your condition:

[ "${var+1}" ]
printf $"var does%.$?0s exist%c\n" \ not !

You can also make printf fail based on a substitution...

printf $"var does%${var+.}s exist%c\n%.${var+b}d" \
        \ not ! \\c >&"$((2${var+-1}))" 2>/dev/null

...which prints $var does not exist! to stderr and returns other than 0 when $var is unset, but prints $var does exist! to stdout and returns 0 when $var is set.

mikeserv
  • 58,310
2

The pure shell way:

[ "${var+1}" ] || echo "The variable has not been set"

Test script:

#!/bin/sh
echo "Test 1, var has not yet been created"
[ "${var+1}" ] || echo "The variable has not been set"

echo "Test 2, var=1"
var=1
[ "${var+1}" ] || echo "The variable has not been set"

echo "Test 3, var="
var=
[ "${var+1}" ] || echo "The variable has not been set"

echo "Test 4, unset var"
unset var
[ "${var+1}" ] || echo "The variable has not been set"
echo "Done"

Results:

Test 1, var has not yet been created
The variable has not been set
Test 2, var=1
Test 3, var=
Test 4, unset var
The variable has not been set
Done
2

With bash 4.4.19 the following worked for me. Here is a complete example

$export MAGENTO_DB_HOST="anyvalue"

#!/bin/bash

if [ -z "$MAGENTO_DB_HOST" ]; then
    echo "Magento variable not set"
else
    echo $MAGENTO_DB_HOST
fi
1

bash function that works for both scalar and array types:

definition

has_declare() { # check if variable is set at all
    local "$@" # inject 'name' argument in local scope
    &>/dev/null declare -p "$name" # return 0 when var is present
}

invocation

if has_declare name="vars_name" ; then
   echo "variable present: vars_name=$vars_name"
fi
0

You can't use if command to check the existence of declared variables in bash however -v option exists in newer bash, but it's not portable and you can't use it in older bash versions. Because when you are using a variable if it doesn't exists it will born at the same time.

E.g. Imagine that I didn't use or assign a value to the MYTEST variable, but when you are using echo command it shows you nothing! Or if you are using if [ -z $MYTEST ] it returned zero value! It didn't return another exit status, which tells you that this variable doesn't exist!

Now you have two solutions (Without -v option):

  1. Using declare command.
  2. Using set command.

For example:

MYTEST=2
set | grep MYTEST
declare | grep MYTEST

But unfortunately these commands shows you loaded functions in memory too! You can use declare -p | grep -q MYTEST ; echo $? command for cleaner result.

0

Function to check if variable is declared/unset

including empty $array=()


In addition to @Gilles's answer

case " ${!foobar*} " in
  *" foobar "*) echo "foobar is declared";;
  *) echo "foobar is not declared";;
esac

-- which I did not find a way for to encapsulate it within a function -- I'd like to add a simple version, which is partly based on Richard Hansen's answer, but does address also the pitfall that occurs with an empty array=():

# The first parameter needs to be the name of the variable to be checked.
# (See example below)

var_is_declared() {
    { [[ -n ${!1+anything} ]] || declare -p $1 &>/dev/null;}
}

var_is_unset() {
    { [[ -z ${!1+anything} ]] && ! declare -p $1 &>/dev/null;} 
}
  • By first testing if the variable is (un)set, the call to declare can be avoided, if not necessary.
  • If however $1 contains the name of an empty $array=(), the call to declare would make sure we get the right result
  • There's never much data passed to /dev/null as declare is only called if either the variable is unset or an empty array.


With the following code the functions can be tested:

( # start a subshell to encapsulate functions/vars for easy copy-paste into the terminal
  # do not use this extra parenthesis () in a script!

var_is_declared() {
    { [[ -n ${!1+anything} ]] || declare -p $1 &>/dev/null;}
}

var_is_unset() {
    { [[ -z ${!1+anything} ]] && ! declare -p $1 &>/dev/null;} 
}

:;       echo -n 'a;       '; var_is_declared a && echo "# is declared" || echo "# is not declared"
a=;      echo -n 'a=;      '; var_is_declared a && echo "# is declared" || echo "# is not declared"
a="sd";  echo -n 'a="sd";  '; var_is_declared a && echo "# is declared" || echo "# is not declared"
a=();    echo -n 'a=();    '; var_is_declared a && echo "# is declared" || echo "# is not declared"
a=("");  echo -n 'a=("");  '; var_is_declared a && echo "# is declared" || echo "# is not declared"
unset a; echo -n 'unset a; '; var_is_declared a && echo "# is declared" || echo "# is not declared"
echo ;
:;       echo -n 'a;       '; var_is_unset a && echo "# is unset" || echo "# is not unset"
a=;      echo -n 'a=;      '; var_is_unset a && echo "# is unset" || echo "# is not unset"
a="foo"; echo -n 'a="foo"; '; var_is_unset a && echo "# is unset" || echo "# is not unset"
a=();    echo -n 'a=();    '; var_is_unset a && echo "# is unset" || echo "# is not unset"
a=("");  echo -n 'a=("");  '; var_is_unset a && echo "# is unset" || echo "# is not unset"
unset a; echo -n 'unset a; '; var_is_unset a && echo "# is unset" || echo "# is not unset"
)

The script should return

a;       # is not declared
a=;      # is declared
a="foo"; # is declared
a=();    # is declared
a=("");  # is declared
unset a; # is not declared

a;       # is unset
a=;      # is not unset
a="foo"; # is not unset
a=();    # is not unset
a=("");  # is not unset
unset a; # is unset