126

I'm currently writing a bash testing framework, where in a test function, both standard bash tests ([[) as well as predefined matchers can be used. Matchers are wrappers to '[[' and besides returning a return code, set some meaningful message saying what was expected.

Example:

string_equals() {
    if [[ ! $1 = $2 ]]; then
            error_message="Expected '$1' to be '$2'."

            return 1
    fi
}

So, when a matcher is used, and it fails, only then an error_message is set.

Now, at some point later, I test whether the tests succeeded. If it succeeded, I print the expectation in green, if it failed in red.

Furthermore, there may be an error_message set, so I test if a message exists, print it, and then unset it (because the following test may not set an error_message):

if [[ $error_message ]]; then
    printf '%s\n' "$error_message"

    unset -v error_message
fi

Now my question is, if it is better to unset the variable, or to just set it to '', like

error_message=''

Which one is better? Does it actually make a difference? Or maybe should I have an additional flag indicating that the message was set?

codeforester
  • 39,467
  • 16
  • 112
  • 140
helpermethod
  • 59,493
  • 71
  • 188
  • 276
  • 1
    If you never compare `error_message` with anything else, I'd say it doesn't matter. However, I think you want `[[ $error_message ]]`, otherwise you are testing that the literal string "error_message" exists. – chepner Sep 04 '12 at 12:44
  • @chepner Yeah, was a typo. Fixed it. – helpermethod Sep 04 '12 at 14:13

4 Answers4

155

Mostly you don't see a difference, unless you are using set -u:

/home/user1> var=""
/home/user1> echo $var

/home/user1> set -u
/home/user1> echo $var

/home/user1> unset var
/home/user1> echo $var
-bash: var: unbound variable

So really, it depends on how you are going to test the variable.

I will add that my preferred way of testing if it is set is:

[[ -n $var ]]  # True if the length of $var is non-zero

or

[[ -z $var ]]  # True if zero length
cdarke
  • 42,728
  • 8
  • 80
  • 84
  • 52
    `var=` is not "not set". It's just an unquoted empty string. In addition to `set -u`, bash's various forms of parameter expansion can also distinguish between unset and null values: `${foo:bar}` expands to "bar" when `foo` is unset, but "" when `foo` is null, while `${foo:-bar}` expands to "bar" if foo is unset or null. – chepner Sep 04 '12 at 14:28
  • 1
    `[[ -n $var ]]` is false if `var` is set to the empty string. – chepner Sep 04 '12 at 14:30
  • 1
    if you use 'declare -p' to check for variable 'existence' then var= will show 'declare -- var=""' you must use unset to get rid of it, of course if its a readonly variable then you cant get rid of it of course. Furthermore, if you var=something inside a function, you wont have to worry about getting rid of it if you use "local var=value" be careful though, because "declare var=value" is also local.In the case of declare you have to explicitly set it global using "declare -g var=value", without declare you have to explicitly set it to be local using "local".Global vars are only erased /w unset. – osirisgothra May 30 '14 at 17:09
  • @osirisgothra: good point about local variables. Of course generally it is best to declare (or localise) all variables in a function so that it is encapsulated. – cdarke Jan 07 '15 at 06:41
  • 5
    @chepner should be `${foo-bar}` instead of `${foo:bar}`. test for yourself: `unset a; echo ">${a:-foo}-${a:foo}-${a-foo}<"` – Liviu Chircu Jun 24 '17 at 13:05
  • @LiviuChircu - In [GNU bash, version 4.3.48(1)-release (x86_64-pc-linux-gnu)] I get [ >foo--foo< ]. – Craig Hicks Mar 24 '18 at 16:54
  • `[[ -n $var ]]` will not return true or false, but raise the `unbound variable` error, if `set -u` is used and `$var` is unset. – mwfearnley Nov 19 '19 at 14:56
19

As has been said, using unset is different with arrays as well

$ foo=(4 5 6)

$ foo[2]=

$ echo ${#foo[*]}
3

$ unset foo[2]

$ echo ${#foo[*]}
2
Zombo
  • 1
  • 62
  • 391
  • 407
2

So, by unset'ting the array index 2, you essentially remove that element in the array and decrement the array size (?).

I made my own test..

foo=(5 6 8)
echo ${#foo[*]}
unset foo
echo ${#foo[*]}

Which results in..

3
0

So just to clarify that unset'ting the entire array will in fact remove it entirely.

Paulie-C
  • 1,674
  • 1
  • 13
  • 29
1

Based on the comments above, here is a simple test:

isunset() { [[ "${!1}" != 'x' ]] && [[ "${!1-x}" == 'x' ]] && echo 1; }
isset()   { [ -z "$(isunset "$1")" ] && echo 1; }

Example:

$ unset foo; [[ $(isunset foo) ]] && echo "It's unset" || echo "It's set"
It's unset
$ foo=     ; [[ $(isunset foo) ]] && echo "It's unset" || echo "It's set"
It's set
$ foo=bar  ; [[ $(isunset foo) ]] && echo "It's unset" || echo "It's set"
It's set
Jonathan H
  • 7,591
  • 5
  • 47
  • 80