Variable handling and scoping in shell and especially bash
can be very obscure and unintuitive (and sometimes buggy).
ksh
had typeset
for a similar feature. ksh
, zsh
, yash
have typeset
. bash
has typeset
as an alias of declare
for compatibility with ksh
, zsh
has declare
as an alias of typeset
for compatibility with bash
. Most shells have export
, readonly
and local
that implement part of what typeset
does.
One of the reasons why bash
authors chose declare
over typeset
may be because typeset
does not only set the type, it also declares a variable: introduce it in a given scope, possibly with a type, attributes and/or value.
In bash
, variables can be:
- unknown (like when they never have been set or declared)
- declared (after
declare
)
- set (when given a value, possibly empty).
They can be of different types:
- scalar
- array
- associative array
and have several attributes:
- integer
- exported
- readonly
- all lower-case/upper-case
- named references
(though the distinction between type and attribute can be quite blurry).
Not all combinations of types and attributes are supported or effective.
Now, declare
declares the variable in the current scope. bash
, even though it implements dynamic scoping treats the outer-most scope specially. It calls it the global scope.
declare
behaves very differently when it's called in that global scope and when in a function (I'm not talking of the kind of separate scope that is introduced by subshells or associated with the environment).
When you do declare var
inside a function and provided that same variable hadn't been declared in that same scope, it declares a new variable which is initially unset and that shadows a potential var
variable that would have been present in the parent scope (the caller of the function).
That's dynamic scoping implemented via some sort of stack. When the function exits, the status, type, attributes and value of the variable as it was when the function was invoked is restored (popped from the stack).
Outside of any function however (in the global scope), declare
does declare a variable, but does not initialise it as unset if it was set before (same as when using declare
a second time within the same function scope). If a type is specified, the value of the variable may be converted, though not all conversion paths are allowed (only scalar to array/hash), and attributes may be added or removed.
In bash
, within a function declare -g
acts on the variable at the bottom of the stack, in the outer-most ("global") scope.
declare -g
was inspired from ksh93
's typeset -g
. But ksh93
implements static scoping where the global scope is different and separate from each function scope. Doing the same with dynamic scoping makes little sense. In all other shells that have typeset -g
(mksh
, zsh
, yash
), typeset -g
is used to change some attribute of a variable without instantiating a new local one.
In bash
, people generally use it for the same purpose, but because it affects the variable of the outer-most scope instead of the current variable, it doesn't always work.
For instance:
integer() { typeset -gi "$1"; }
To make a variable an integer works in mksh
/yash
/zsh
. It works in bash
only on variables that have not been declared local by the caller:
$ bash -c 'f() { declare a; integer a; a=1+1; echo "$a"; }; integer() { typeset -gi "$1"; }; f'
1+1
$ bash -c 'f() { integer a; a=1+1; echo "$a"; }; integer() { typeset -gi "$1"; }; f'
2
Note that export var
is neither typeset -x var
nor typeset -gx var
. It adds the export
attribute without declaring a new variable if the variable already existed. Same for readonly
vs typeset -r
.
Also note that unset
in bash
only unsets a variable if it has been declared in the current scope (leaves it declared though except in the global scope; it removes attributes and values and the variable is no longer array or hash; also note that on namerefs, it unsets the referenced variable). Otherwise, it just pops one variable layer from the stack mentioned above. With bash
5.0 or above, that can be fixed by setting the localvar_unset
option.
So to sum up:
declare var
When called in a function and if var
has not been declared before in that same function, declares a variable of type scalar with no attributes and that is initially unset.
If called outside of any function or if var
had already been declared in the same function, it has no effect as we're not specifying any new type or attribute.
declare -g var
Wherever it's called would declare a var
in the outer-most ("global") scope: make it declared, of type scalar, no attribute, no value if it was previously unknown in that scope (which for all intent and purpose is the same as an unknown variable except that it would show in the output of typeset -p
), or do nothing otherwise.
In any case, you might not be able to access that variable in the context you're running that command:
f() { local a; g; }; g() { typeset -g a=123; echo "$a"; }; f
outputs nothing.