1

Using bash 4.3, there seems to be a discrepancy between the manual and actual behaviour:

unset string # just to be sure
declare string # $? is 0 afterwards
declare -p string # fails, 'string: not found'
printf %b "${string-unset}" "\n" # consequently, yields unset

The manual segment for the declare/typeset builtin does not state that an assignment upon declaration is required. On the contrary, the declare [...] [...] name[=value] notation hints that this should be legal.

What am I missing, is it a definition thing of what a variable being "set" is supposed to mean? Have I misread the "Parameter Expansion" segment of the manual, which at least uses the term "unset"? Or is this just a version-specific oddity?

ithaca
  • 23
  • @Jesse_b, if that should be a problem, the output of declare without any options also does not seem to contain the name "string". – ithaca Jan 10 '19 at 18:59
  • @Jesse_b, additionally look at the expansion statement. Can you explain why this falls back to the substitute for the unset case? If not, the main question still stands, despite the way of checking it with declare might be erroneous. – ithaca Jan 10 '19 at 19:01
  • 1
    @Jesse_b, then this might hint to a version-specific problem. Out of interest, did you check declare -p string does not work for you? As soon as declare -p is executed on an assigned-to name, this seems ok: unset x; declare x=2; declare -p x. – ithaca Jan 10 '19 at 19:04
  • Yes, it seems to have been fixed in v4.4. although in 4.3 you will see string at the bottom of declare -p as declare -- string – jesse_b Jan 10 '19 at 19:07
  • Interesting. Just had the chance to check on another machine with v4.4 as well, you are right. Should have verified this before asking. But what remains is that the parameter expansion still falls back to the "unset" case for the empty declaration. Would be interesting to see if that is fixed in an even later version. Unfortunately, i have no access to a newer install atm. This would be a separate/slightly different question then, or maybe something that could be looked up at the bash development site. No other way to know but to research the bug history there, I'm afraid – ithaca Jan 10 '19 at 19:11
  • @Jesse_b, thanks for the pointers; will add an answer in a few stating this is most likely a version specific thing ... – ithaca Jan 10 '19 at 19:14
  • If you set string= you get the desired result from ${string-unset}, but will notice declare -p shows declare -- string="". Not really sure if that's relevant. – jesse_b Jan 10 '19 at 19:27

2 Answers2

3

On bash versions up to 3.2, the sequence:

 $ declare string; declare -p string

Prints declare -- string="", which shows that the variable gets set to the null string.

On versions 4.1,4.2 and 4.3, the variable doesn't get set to anything, which actually raises an error:

 $ declare string; declare -p string
 bash: declare: string: not found

Solved on bash 4.4:

 $ declare string; declare -p string
 declare -- string

Which means that string has been declared but no value (not even null) has been assigned to it.

1

Indeed, the assumption on the meaning of a parameter/variable being unset in the OP is imprecise.

man bash defines a variable as a special type of parameter. Therefore, information about variables is sometimes located in sections only referring to parameters in general.

General terms characterising variables

Terms describing the characteristics and states with regard to variables by the Wikipedia article on variables.

  1. declared state: memory is reserved, a name exists
  2. initialized state: in addition to 1., a value is assigned (directly after declaration)
  3. uninitialized state: the value is undefined, no initial value has been set (just the declaration took place)
  4. null value: reserved/special value returned by some programming languages to indicate an uninitialized variable

Items in the bash manual relating to variables

  1. set state: "A parameter is set if it has been assigned a value." [PARAMETERS section]
  2. unset state, ambiguous, seems applicable to variables not/no longer existing and to variables in "uninitialized state" according to general terms: "... bash tests for a parameter that is unset ..." [Parameter Expansion segment]
  3. null/null string - an empty value: "A parameter is set if it has been assigned a value. The null string is a valid value." [PARAMETERS section]
  4. unset builtin - command "extinguishing" variables, e.g. unset string: "For each name, remove the corresponding variable ..." [SHELL BUILTIN COMMANDS section, 'unset' paragraph]
  5. declare builtin - e.g. declare -p string: "The -p option will display the attributes and values of each name." [SHELL BUILTIN COMMANDS section, 'declare'/'typeset' paragraph]

Re-stating and answering the original question

The question should have more precisely been worded as: "How could an uninitialized bash variable be distinguished from a non-existing one?"

An answer to this is:

  1. Do not get puzzled by the unset builtin removing a variable from existence and a variable in unset state in bash terms being one that either has not been declared or has been declared with no value.

  2. The declare builtin command does indeed behave inconsistently in bash 4.3 vs. bash 4.4 with regard to variables that have been declared without value:

    $ # bash 4.3
    $ unset string; declare string; declare -p string; echo $?
    bash: declare: string: not found
    1
    $ # bash 4.4
    $ unset string; declare string; declare -p string; echo $?
    declare -- string
    0
    

    This way of testing for a single variable name being in use is therefore not universal, but appears to work in recent bash versions.

  3. Unfortunately, parameter expansion will not help with the distinction between non-existing and uninitialized variables, as both situations are encompassed by the same unset state:

    $ declare string; echo ${string-UNSET} # `string` exists, empty
    UNSET
    $ unset string; echo ${string-UNSET} # `string` does not exist
    UNSET
    
  4. Unfortunately, also the conditional operator -v is of no help [manual section CONDITIONAL EXPRESSIONS], it tests whether "the shell variable VARNAME is set (has been assigned a value)", without indicating whether VARNAME existed in the first place:

    $ unset string; ! [ -v string ] && echo "unused"
    unused
    $ declare string; ! [ -v string ] && echo "unused"
    unused
    

Related topics

Discussions highlighting terminology mixups and bash specifics with regards to parameter/variable states and values:

  1. How do I check if a variable exists ...

  2. How to test if a variable is defined at all in Bash ...

  3. Are the null string and “” the same string?

ithaca
  • 23