118

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
  • 34,080
  • 14
  • 96
  • 122
helpermethod
  • 55,449
  • 64
  • 175
  • 266
  • 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

149

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
  • 40,173
  • 7
  • 78
  • 79
  • 48
    `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
  • 4
    @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
  • 55
  • 342
  • 375
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,514
  • 1
  • 11
  • 28
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,176
  • 5
  • 45
  • 76