62

After reading ilkkachu's answer to this question I learned on the existence of the declare (with argument -n) shell built in.

help declare brings:

Set variable values and attributes.

Declare variables and give them attributes. If no NAMEs are given, display the attributes and values of all variables.

-n ... make NAME a reference to the variable named by its value

I ask for a general explanation with an example regarding declare because I don't understand the man. I know what is a variable and expanding it but I still miss the man on declare (variable attribute?).

Maybe you'd like to explain this based on the code by ilkkachu in the answer:

#!/bin/bash
function read_and_verify  {
    read -p "Please enter value for '$1': " tmp1
    read -p "Please repeat the value to verify: " tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo "Values unmatched. Please try again."; return 2
    else
        declare -n ref="$1"
        ref=$tmp1
    fi
}
1

5 Answers 5

53
+100

In most cases it is enough with an implicit declaration in bash

asdf="some text"

But, sometimes you want a variable's value to only be integer (so in case it would later change, even automatically, it could only be changed to an integer, defaults to zero in some cases), and can use:

declare -i num

or

declare -i num=15

Sometimes you want arrays, and then you need declare

declare -a asdf   # indexed type

or

declare -A asdf   # associative type

You can find good tutorials about arrays in bash when you browse the internet with the search string 'bash array tutorial' (without quotes), for example

linuxconfig.org/how-to-use-arrays-in-bash-script


I think these are the most common cases when you declare variables.


Please notice also, that

  • in a function, declare makes the variable local (in the function)
  • without any name, it lists all variables (in the active shell)

    declare
    

Finally, you get a brief summary of the features of the shell built-in command declare in bash with the command

help declare
0
23

The output of help declare is quite terse. A clearer explanation can be be found in man bash or info bash — the latter being the source for what follows.

First, some definitions. About variables and attributes:

A parameter is an entity that stores values. ... A variable is a parameter denoted by a name. A variable has a value and zero or more attributes. Attributes are assigned using the declare builtin command ...

And about the declare builtin:

declare

declare [-aAfFgilnrtux] [-p] [name[=value] …]

Declare variables and give them attributes. If no names are given, then display the values of variables instead.

...

-n
Give each name the nameref attribute, making it a name reference to another variable. That other variable is defined by the value of name. All references, assignments, and attribute modifications to name, except for those using or changing the -n attribute itself, are performed on the variable referenced by name’s value. ...

Note that name reference variables are only available in Bash 4.3 or later1.

Also, for a useful introduction to declare and variable attributes in Bash I would point you to this answer to "What do declare name and declare -g do? " (which mainly focuses on variables' scope, though).


Basically2, declare name=[value] is equivalent to the assignment name=[value] you are probably familiar with. In both cases, name is assigned the null value if value is missing.

Note that the slightly different declare name, instead, does not set the variable name3:

$ declare name

## With the -p option, declare is used to display
## attributes and values of variables
$ declare -p name
declare -- name            ## "name" exists

## Parameter expansion can be used to reveal if a variable is set:
## "isunset" is substituted to "name" only if unset 
$ echo "${name-isunset}"
isunset

Thus, the variable name can be:

  • declared and unset, after declare name;
  • declared and set with null as value, after name= or declare name=;
  • declared, set and with a non null value after name=value or declare name=value.

More generally, declare [options] name=value

  1. creates the variable name — which is a parameter with a name, which in turn is just a portion of memory you can use to store information4;
  2. assigns the value value to it;
  3. optionally sets name's attributes, which define both the kind of value it can store (not in terms of a type, strictly speaking, since Bash's language is not typed) and the ways it can be manipulated.

Attributes are probably easier to explain with an example: using declare -i name will set the "integer" attribute of name, letting it be treated as an integer; quoting the manual, "arithmetic evaluation will be performed when the variable is assigned a value":

## Let's compare an ordinary variable with an integer
$ declare var
$ declare -i int
$ var="1+1"
$ int="1+1"
$ echo "$var"
1+1                 ## The literal "1+1"
$ echo "$int"
2                   ## The result of the evaluation of 1+1

In light of the above, what is happening in ilkkachu's code is that:

  1. A variable named ref is declared, with the "nameref" attribute set, and the content of $1 (the first positional argument) is assigned to it:

    declare -n ref="$1"
    

    The aim of a name reference variable such as ref is to hold the name of another variable, which would generally not be known in advance, possibly because we want it to be dynamically defined (e.g. because we want to reuse a piece of code and have it applied to several variables), and to provide a convenient way for referring to (and manipulating) it. (Not the only one, though: indirection is an alternative; see Shell Parameter Expansion).

  2. When the value of the variable tmp1 is assigned to ref:

    ref=$tmp1
    

    an additional variable, whose name is the value of ref, is implicitly declared. The value of tmp1 is also indirectly assigned to the implicitly declared variable by means of this explicit assignment to ref.

In the context of your linked question, calling read_and_verify as

read_and_verify domain "Prompt text here..."

will declare the variable domain and assign it the value of tmp1 (i.e. the user's input). It is exactly designed to reuse the code that interacts with the user and leverage a nameref variable to declare domain and a few other variables.

To take a closer look at the implicit part we can reproduce the process step by step:

## Assign a value to the first positional argument
$ set -- "domain"

## Declare the same "tmp1" variable as in your code
$ tmp1="value for domain"

## Declare a "ref" variable with the nameref attribute set and
## assign the value "domain" to it
$ declare -n ref="$1"

## Note that there is no "domain" variable yet
$ declare -p domain
bash: declare: domain: not found

## Assign a value to "ref" and, indirectly, to the "domain" variable
## that is implicitly declared  
$ ref=$tmp1

## Verify that a variable named "domain" now exists, and that
## its value is that of "tmp1"
$ declare -p domain
declare -- domain="value for domain"

## Verify that "ref" is actually a reference to "domain"
$ domain="new value"
$ echo "$domain"
new value
$ declare -p ref
declare -n ref="domain"
$ echo "$ref"
new value

1 Reference: CHANGES file, section "3. New Features in Bash", point "w".
This may be relevant: for instance, CentOS Linux 7.6 (currently the latest version) is shipped with Bash 4.2.

2 As usual with shell builtins, an exhaustive and concise explanation is elusive since they perform various, possibly heterogeneous actions. I will focus on declaring, assigning and setting attributes only, and I will consider listing, scoping and removing attributes as out of the scope of this answer.

3 This behavior of declare -p has been introduced in Bash 4.4. Reference: CHANGES file, section "3. New Features in Bash", point "f".
As G-Man pointed out in comments, in Bash 4.3 declare name; declare -p name yields an error. But you can still check that name exists with declare -p | grep 'declare -- name'.

4 FullBashGuide, Parameters on mywiki.wooledge.org

4
  • (1) I can’t reproduce the results you show in your first code block: declare name followed by declare -p name yields “bash: declare: name: not found”.  (Although declare -p | grep na yields declare -- name.) (2) I believe that it’s a little bit misleading to present echo "${name-isunset}" in the context of declare name, inasmuch as it treats an undeclared (i.e., undefined) variable the same as a declared but unset variable. (3) You might want to mention that namerefs are available only in bash version 4.3 and higher. Commented Jun 22, 2019 at 18:57
  • @G-Man Thank you for your remarks! I'll address them as soon as I can and I'll update my answer where appropriate. As for (1), my GNU bash, version 5.0.7(1)-release (x86_64-pc-linux-gnu) on Arch Linux still yields the results I showed. Maybe that behavior has been introduced only recently, I'll investigate that.
    – fra-san
    Commented Jun 22, 2019 at 19:43
  • Yeah; I'm only using version 4.3. Commented Jun 22, 2019 at 20:12
  • 1
    @G-Man Answer updated with notes about (1) and (3). About (2): I aimed to illustrate that declare x doesn't set x, while declare x= does. I couldn't find any reference to back the assertion that declare -- x (as output of declare -p x) means "not set", while declare -- x="" means "set"; thus I brought in the ${parameter-word} expansion, even if it cannot discriminate between "unset" and "doesn't exist", as you point out. I'm not sure how can I clarify this in my answer (without distracting the reader from the point), though.
    – fra-san
    Commented Jun 23, 2019 at 10:34
22
+50

I’ll have my go at trying and explain this, but forgive me if I won’t follow the example you provided. I’ll rather try to guide you along my own, different, approach.

You say you already understand concepts such as “variables” and “expanding them”, etc. so I’ll just skim over some background knowledge that would otherwise require deeper focus.

So I’ll start by saying that, at its most basic level, the declare command is just a way for you to tell Bash that you need a variable value (i.e. a value the might change during script execution), and that you will refer to that value using a specific name, precisely the name you indicate next to the declare command itself.

That is:

declare foo="bar"

tells Bash that you want the variable named foo have the value bar.

But.. hold on a minute.. we can do that without using declare at all, can’t we. As in:

foo="bar"

Very true.

Well, it so happens that the above simple assignment is actually an implicit way for.. in fact.. declaring a variable.

(It also so happens that the above is one of a few ways to change the value of the variable named foo; indeed it is precisely the most direct, concise, evident, straight-forward way.. but it’s not the only one.. .. I’ll come back to this later..).

But then, if it is so well possible to declare a “name that will tag variable values” (just “variable” from hereafter, for the sake of brevity) without using declare at all, why would you ever want to use this pompous “declare” command ?

The answer lies in the fact that the above implicit way to declare a variable (foo="bar"), it.. implicitly.. makes Bash consider that variable of being of the type that is most commonly used in the typical usage scenario for a shell.

Such type is the string type, i.e. a sequence of characters with no particular meaning. Therefore a string is what you get when you use the implicit declaration.

But you, as the programmer, sometimes need to rather consider a variable as, e.g., a number.. on which you need to do arithmetic operations.. and using an implicit declaration like foo=5+6 won’t make Bash assign value 11 to foo as you might expect. It will rather assign to foo the sequence of the three characters 5 + 6.

So.. you need a way to tell Bash that you want foo to be considered a number, not a string.. and that’s what an explicit declare comes useful for.

Just say:

declare -i foo=5+6  # <<- note the '-i' option: it means 'integer'

and Bash will happily do the math for you, and assign the numeric value 11 to variable foo.

That is: by saying declare -i foo you give to variable foo the attribute of being an integer number.

Declaring numbers (precisely integers, because Bash still does not understand decimals, floating points, and all that) may be the first reason for using declare, but it’s not the only reason. As you have already understood, there are a few other attributes you can give to variables. For instance you can have Bash to always make a variable’s value uppercase no matter what: if you say declare -u foo, then from then on when you say foo=bar Bash actually assigns the string BAR to the variable foo.

In order to give any of these attributes to a variable, you must use the declare command, there’s no other choice.


Now, one other of the attributes you can give through declare is the infamous “name-ref” one, the -n attribute. (And now I'm going to resume the concept I put on hold earlier).

The name-ref attribute, basically, allows Bash programmers for another way to change the value of a variable. It more precisely gives an indirect way to do that.

Here’s how it works:

You declare a variable having the -n attribute, and it is very recommended (though not strictly required, but it makes things simpler) that you also give a value to this very variable on the same declare command. Like this:

declare -n baz="foo"

This tells Bash that, from then on, each time you will use, or change, the value of the variable named baz, it shall actually use, or change, the value of the variable named foo.

Which means that, from then on, you can say something like baz=10+3 to make foo get the value of 13. Assuming of course that foo was previously declared as integer (declare -i) like we did just one minute ago, otherwise it’ll get the sequence of the four characters 1 0 + 3.

Also: if you change foo’s value directly, as in foo=15, you will see 15 also by saying echo “${baz}”. This is because variable baz declared as name-ref of foo always reflects foo’s value.

The above declare -n command is said a “name-reference” because it makes variable baz refer to the name of another variable. In fact we have declared baz has having value "foo" which, because of the -n option, is handled by Bash as the name for another variable.

Now, why on Earth would you ever want to do that ?

Well.. it’s worth saying that this is a feature for quite advanced needs.

In fact so advanced that when a programmer faces a problem that would really require a name-ref, it is also likely that such problem should rather be addressed to by using a proper programming language instead of Bash.

One of those advanced needs is, for instance, when you, as the programmer, cannot know during development which variable you will have to use in a specific point of a script, but it will be fully known dynamically at run-time. And given that there’s no way for any programmer to intervene at run-time, the only option is to make provision beforehand for such situation in the script, and a “name-ref” can be the only viable way. As a widely known use case of this advanced need, think of plug-ins, for instance. The programmer of a “plugin-able” program needs to make generic provision for future (and possibly third-party) plug-ins beforehand. Therefore the programmer will need to use facilities like a name-ref in Bash.

One other advanced need is when you have to deal with huge amount of data in RAM and you also need to pass that data around functions of your script that also have to modify that data along the way. In such case you could certainly copy that data from one function to another (like Bash does when you do dest_var="${src_var}" or when you invoke functions like in myfunc "${src_var}"), but being that data a huge amount it would make for a huge waste of RAM and for a very inefficient operation. So The Solution if such situations arise is to use not a copy of the data, but a reference to that data. In Bash, a name-ref. This use case is really the norm in any modern programming language, but it is quite exceptional when it comes to Bash, because Bash is mostly designed for short simple scripts that mostly deal with files and external commands and thus Bash scripts seldom have to pass huge amount of data between functions. And when a script’s functions do need to share some data (access it and modify it) this is usually achieved by just using a global variable, which is quite the norm in Bash scripts as much as it is very deprecated in proper programming languages.

Then, there may be a notable use case for name-refs in Bash, and (maybe ironically) it is associated to when you use yet other types of variables:

  1. variables that are declared as “indexed arrays” (declare -a)
  2. variables that are declared as “associative arrays” (declare -A).

These are a type of variables that may be more easily (as well as more efficiently) passed along functions by using name-refs instead of by normal copying, even when they don’t carry huge amounts of data.

If all these examples sound weird, and still incomprehensible, it’s only because name-refs are indeed an advanced topic, and a rare need for the typical usage scenario of Bash.

I could tell you about occasions on which I for one have found use for name-refs in Bash, but so far they have been mostly for quite “esoteric” and complicated needs, and I’m afraid that if I described them I would only complicate things for you at this point of your learning. Just to mention the least complex (and possibly not esoteric): returning values from functions. Bash does not really support this functionality, so I obtained the same by using name-refs. This, incidentally, is exactly what your example code does.


Besides this, a small personal advice, which would actually be better suited for a comment but I haven't been able to condense it enough to fit into StackExchange's comment's limits.

I think that the most you should do at the moment is to just experiment with name-refs by using the simple examples I showed and maybe with the example code you provided, disregarding for the moment the “why on earth” part and focusing only on the “how it works” part. By experimenting a bit, the “how” part may sink better into your mind, so that the “why” part will come clear to you in due time when (or if) you’ll have a real practical problem for which a name-ref would truly come in handy.

6
  • LL3 I like your answer very much and I thumbed up: I finally understand what declare does --- not declaring variables but declaring their attributes; yet sadly I lost track when you started to explain what is a name-ref. I got no idea what it does and why to use it.
    – user149572
    Commented Apr 6, 2019 at 2:07
  • That is - why would one give this attribute
    – user149572
    Commented Apr 6, 2019 at 8:09
  • @JohnDoea I see. Maybe you might just leave this topic for the moment. It’s probably too soon to grasp this concept at the current point of your learning. Anyways I’ve expanded my answer with more examples and also a little personal addendum as to how I think you should proceed. I’ve also put horizontal lines to delimit the general explanation of declare from the specific explanation of -n from the personal addendum. I’ve also done a bit of rewording here and there but nothing substantial, so I think you may just re-read the -n part plus the little addendum.
    – LL3
    Commented Apr 6, 2019 at 21:45
  • Thank you, I think it's okay for me to learn on this right now, I just need an explanation I can understand and I should re-read your entire answer today, I'm here around.
    – user149572
    Commented Apr 6, 2019 at 23:50
  • 1
    You mention foo="bar" and declare foo="bar".  You might want to mention (if only in a footnote) that, in a shell function, declare foo="bar" creates a local variable and foo="bar" creates a global variable. Commented Jun 22, 2019 at 19:00
6

In general, declare in the bash shell sets (or removes, or displays) attributes on variables. An attribute is a sort of annotation that says "this is a name reference", or "this is an associative array", or "this variable should always be evaluated as an integer", or "this variable is read-only and can not be re-set", or "this variable is exported (an environment variable)" etc.

The typeset built-in is a synonym for declare in bash, as typeset is used in other shells (ksh, where it originated, and zsh, for example) for setting variable attributes.


Looking more closely at the name reference example in the question:

The shell function that you show, with an added bit of code that uses it:

#!/bin/bash

function read_and_verify  {
    read -p "Please enter value for '$1': " tmp1
    read -p "Please repeat the value to verify: " tmp2
    if [ "$tmp1" != "$tmp2" ]; then
        echo "Values unmatched. Please try again."; return 2
    else
        declare -n ref="$1"
        ref=$tmp1
    fi
}

read_and_verify foo
printf 'The variable "foo" has the value "%s"\n' "$foo"

Running this:

$ bash script.sh
Please enter value for 'foo': hello
Please repeat the value to verify: hello?
Values unmatched. Please try again.
The variable "foo" has the value ""

That shows that the foo variable is not being set to anything when the user inputs two different strings.

$ bash script.sh
Please enter value for 'foo': hello
Please repeat the value to verify: hello
The variable "foo" has the value "hello"

That shows that the variable foo gets set to the string that the user entered when they entered the same string twice.

The way that $foo gets the value hello in the main part of the script is by the following lines in the shell function:

declare -n ref="$1"
ref=$tmp1

where $tmp1 is the string hello inputted by the user, and $1 is the string foo passed in on the function's command line from the main part of the script.

Notice that the ref variable is declared with declare -n as a name reference variable and that the value foo is given as the value in that declaration. This means that from that point on, until the variable passes out of scope, any use of the variable ref will be the same as using foo. The variable ref is a name reference variable referencing foo at this point.

This has the consequence that assigning a value to ref, as is done on the line following the declaration, will assign the value to foo.

The value hello is then available in $foo in the main part of the script.

2

Declare is one of a few syntaxes for declaring a variable in Bash:

x=7
declare x=7
declare x

function something {
  local x
  local x=7
}

These various ways of declaring variables are a consequence of the gradual evolution of shell.

The -n is a way of declaring what is sometimes called a "variable variable" -- it is a variable that is bonded to another variable. You can test out the following script online:

#!/bin/bash
set -o errexit -o nounset -o pipefail

declare thevariable
declare -n thevariablevariable=thevariable

thevariable='the value'

printf 'thevariable: %s\n' "$thevariable"
printf 'thevariablevariable: %s\n' "$thevariablevariable"

thevariablevariable='the other value'

printf 'thevariable: %s\n' "$thevariable"
printf 'thevariablevariable: %s\n' "$thevariablevariable"

What you see when you run it is that thevariablevariable is not a pointer to thevariable, rather it is an alias for thevariable:

thevariable: the value
thevariablevariable: the value
thevariable: the other value
thevariablevariable: the other value

Now this may seem like a devious misfeature and it is, indeed, an unusual language feature. However, shell programming sometimes requires strange features.

It may seem odd that we have so many ways of declaring variables in shell, which is notionally a very simple and lofi language; but it has to do with gradual changes in how we program. One example is function local variable definitions.

The simplest way to set a value is obviously x=7; but when we have a script with functions, what does the following mean?

#!/bin/bash
set -o errexit -o nounset -o pipefail

x=7

function something {
  x=6
}

something

printf 'What is x? It is: %s\n' "$x"

Well, one reasonable interpretation is that x should still be 7 at the end of the script; but in fact it is 6, because there was a time when it was not the general expectation of programmers that declarations in functions were local. Experience has shown us that function locals are a good practice; but we can not simply change the meaning of declarations in shell because it will break all the old scripts. Therefore, we introduce a new declaration syntax.

1
  • I've never encountered -n. That sounds useful in theory (for a more semantic variable name than $1 without duplicating it by value). I hope I remember to try it out next time. Commented Mar 28 at 21:22

You must log in to answer this question.