8
  1. What does

    declare name
    

    without providing any option do? Does it declare a string variable's name?

  2. What does

    declare -g
    

    without providing a variable name do? Does it display the values of all the variables with global attribute?

I didn't find answers in the description of declare at Bash manual.

Thanks.

Tim
  • 101,790
  • I know I have seen that output in the past (declare -g), but it was not with the declare command... – Rui F Ribeiro Jul 25 '17 at 18:31
  • Tim, did any of the answers solve your problem? If so, please use the checkmark to tell the system that the question is Answered. Thank you! – Jeff Schaller Jul 30 '17 at 12:12

3 Answers3

17

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.

  • Thanks. What documents did you or can I learn about scoping in bash or shells? – Tim Jul 28 '17 at 02:17
  • From the manpage of typeset of zsh, "the -g option forces variables to be created or modified at the global scope". Why "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"? Why does f() { declare a; integer a; a=1+1; echo "$a"; }; integer() { typeset -gi "$1"; }; f in zsh output 2, instead of 1+1? – Tim Jul 28 '17 at 03:22
  • Is zsh dynamic scoping? – Tim Jul 28 '17 at 03:28
  • In bash, if I am correct, array and associative array are attributes of variables, and we can declare them with declare -a and declare -A. So what do you mean by saying array and associative array are types of variables? Are array and associative array still attributes of variables? – Tim Jul 28 '17 at 03:43
  • @Tim, the documentation of zsh there (like that of yash, but contrary to that of mksh which behaves like zsh) is not totally accurate here. See the description in the mksh man page for the actual behaviour. -g comes from ksh93 where it did mean global which made sense for a shell with static scoping (where variables are either private to the function or global, you can't access the variables of another function), but not for dynamic scoping where a function sees the variables of its caller. – Stéphane Chazelas Jul 28 '17 at 07:02
  • ksh93 is the only shell that I know with static scoping but zsh recently added a new private module to allow functions to have private variables which in effect does enable some sort of static scoping. Note that ksh93 only supports local scoping in functions declared the ksh way (function f { ...; }). – Stéphane Chazelas Jul 28 '17 at 07:04
  • 2
    Yes, the distinction between type and attribute is blurry like I said. You could say that array/hash are attributes that can't be removed like readonly. In ksh88, scalars were just arrays with only the element of indice 0 set. It's very similar in bash, but bash still creates two different variables if you do a=1 and a[0]=1. zsh/yash are the ones with very distinct types for scalar and array. – Stéphane Chazelas Jul 28 '17 at 07:08
  • Thanks. In the manpage of mksh, I didn't find -g option of typeset. So what does -g mean for zsh/mksh? In terms of design, which shells are least obscure and unintuitive? Will learning those shells well first be helpful to learning bash? – Tim Jul 28 '17 at 13:47
  • @Tim, -g was added relatively recently to mksh. I do mention in this answer what typeset -g does in zsh/mksh/yash. There's a risk that learning zsh will make you give up on bash, so in that sense, it may not help you to learn bash :-). If you want a clear and intuitive shell, look at rc – Stéphane Chazelas Jul 28 '17 at 13:55
  • Thanks. Could you elaborate why "learning zsh will make you give up on bash"? – Tim Jul 28 '17 at 14:01
  • If you happen to know, is zsh at the similar good design level to Powershell? – Tim Jul 28 '17 at 14:14
  • @Tim, I can't comment as I don't know Powershell. zsh, like bash also carries some Bourne and Korn heritage baggage, so won't be as neat as a shell like rc that was designed from scratch (and unfortunately those non-technical considerations are the main reason why bash/zsh are used, but not rc). Still, compared to bash it has made a lot of better design decisions IMO. – Stéphane Chazelas Jul 28 '17 at 14:49
  • do you mean zsh "has made a lot of better design decisions IMO"? – Tim Jul 28 '17 at 14:52
  • @Tim, yes, that's what I mean. bash is mostly a one-man effort behind closed doors. While zsh is being designed by several people openly (all code change posted to an open mailing list and discussed upon). bash being the official GNU shell, being POSIX certified and so widely used, developers are in a tight place, when a bad design decision is made, it's hard to change it. The zsh development is more relaxed. – Stéphane Chazelas Jul 28 '17 at 15:00
  • Thanks. When I asked whether learning zsh well may help understanding bash, what I tried to mean is that if zsh is better and similarly designed to bash, understanding zsh may help me linger less around the part of bash which is obscure and likely dropped or changed in future bash releases, and focus on the part of bash which is likely long lasting. – Tim Jul 28 '17 at 15:09
  • @Tim, for that, look at the POSIX standard. – Stéphane Chazelas Jul 28 '17 at 15:13
  • @StéphaneChazelas (1) ksh has both dynamic and static scope. (2) A -g option is needed to access the (assumed) parent scope. Otherwise, there is no way to change a variable attributes with a function. (3) A simple re-evaluation of a: a=$a will make it contain an integer in bash. (4) zsh has the least portable scripting syntax. Learning zsh is like: Regular Expressions: Now You Have Two Problems. If you plan on a long discussion, please open a chat. –  Jul 30 '17 at 01:52
  • @Arrow Re: 1 ksh88 has dynamic scope, ksh93 static cope see http://article.gmane.org/gmane.comp.standards.posix.austin.general/3283 You could say ksh93 has some form of dynamic scoping through subshell environments or ksh -c 'code'. See bash vs zsh: scoping and `typeset -g` for the rest. – Stéphane Chazelas Aug 05 '17 at 08:45
  • ksh93 has both scopes. Your own example to show that bash has dynamic scope: ksh -c 'f() { typeset a=1; g; echo $a;}; g() { echo $a; a=2; }; a=0; f' prints 1 2 (like most shells do). Supposedly (what you said) showing dynamic scope. –  Aug 05 '17 at 09:13
  • @Arrow, no, typeset in Bourne-syntax functions doesn't do scoping at all in ksh93, that changed from ksh88. See David Korn's message linked above or http://article.gmane.org/gmane.comp.standards.posix.austin.general/8377 – Stéphane Chazelas Aug 05 '17 at 09:52
  • From that link, David Korn words For ksh93, I restored name() to Bourne shell semantics and modified function name{...} to use static scoping of local variables. name() has the same scope as all other shells, function name is the only having static scope. So, ksh93 has both scopes (As I have already said). @StéphaneChazelas –  Aug 05 '17 at 10:07
  • If that doesn't convince you, read also there: My proposal is the following: name() functions should allow local variables with dynamic* scoping. function name functions should allow local variables with static scoping*. David Korn own words. Crystal clear. @StéphaneChazelas –  Aug 05 '17 at 10:10
  • @Arrow, I suggest you read both those 2010 and 2013 discussions so you can understand better the parts you quote. There's also one currently ongoing (on and off) about adding local scope to POSIX sh which you might be interested in reading. You could also try it for yourself, like with ksh93 -c 'f() typeset a=2; a=1; f; echo "$a"' – Stéphane Chazelas Aug 05 '17 at 11:34
  • You clearly will not change your position. Nothing else to discuss. @StéphaneChazelas –  Aug 05 '17 at 12:12
  • (1) Is “local” also an attribute?  (It’s not on your list of attributes.)  Or is scope a different kettle of fish from attributes? (2) You might want to explain what “dynamic scoping” means (in the answer in addition to in a comment). – G-Man Says 'Reinstate Monica' Jun 22 '19 at 19:02
4
  1. declare name declares the variable named name, without any attribute; you can see its effect using ${name:not set} for example. The declared variable isn’t an array, and it doesn’t have the integer attribute set, so you could consider it a string variable, but it evaluates to 0 in arithmetic expressions.

  2. From the manpage:

    The -g option forces variables to be created or modified at the global scope, even when declare is executed in a shell function. It is ignored in all other cases.

    So declare -g with no parameters is the same as declare.

Stephen Kitt
  • 434,908
  • Yeah, I was just reading that in the manual and you beat me with the answer. Curious factoid the output of set is byte-by-byte the same as declare ; I think Jeff also noticed that but his answer is not so clear. – Rui F Ribeiro Jul 25 '17 at 18:38
1
declare name

From https://www.gnu.org/software/bash/manual/bashref.html#Bash-Builtins

Declare variables and give them attributes.

You've not provided any options, so there are no attributes assigned, and the variable name is created. No value was provided, so none is assigned.

declare -g

The -g option forces variables to be created or modified at the global scope, even when declare is executed in a shell function. It is ignored in all other cases.

Since you aren't providing a name, no variable is created, so it is ignored. See for yourself:

declare -g > foo
declare > bar
diff foo bar

The only difference may be the value of the Bash variables $_, and possibly PIPESTATUS based on your previous commands; there is no other difference in the returned list of variables.

Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255