1
root@ubuntu:~# echo ${x=1}
1
root@ubuntu:~# echo ${x:=1}
1
root@ubuntu:~# echo ${x-1}
1
root@ubuntu:~# 

Why are there three syntax for setting variables in shell scripts?

What is the technical advantage of having three syntax?

Even programming languages do not have more than one.

  • 2
    I am wondering why you haven't mentioned the standard way; to assign to a variable, to use just: x=1. - All the variants you mention make use of side effects of a variable expansion; they have there uses, and it seems you've got answers that explain the differences. – Janis Jun 17 '15 at 5:21
  • 1
    Try echo $x after each of those and you will see if it actually assigns anything to the variable. This syntax is actually for reading (expanding) the variable (with side-effects, which may also modify the variable, but not in all the cases you mentioned). – orion Jun 17 '15 at 8:12
  • 1
    There are in fact, more ways. x=1, let x=1, ((x=1)), read x <<< 1, eval x=1, and probably others. – Otheus Jun 17 '15 at 16:24
  • 1
    There are more ways. *nix is about having options. Many great tools with many great options. This way you don't have as many obstacles as 'programming languages'. – Baazigar Jun 17 '15 at 18:46
7

These do slightly different things. In fact, the last one echo ${x-1} doesn't in fact set x but substitutes the value 1 in the expression only if x isn't set.

x=1 on the other hand sets x unconditionally.

As for the := operator. This is from the ksh manual:

   ${parameter:=word}
          If  parameter is not set or is null then set it to word; the
          value of the parameter is then substituted.  Positional  parameters may 
          not be assigned to in this way.

In Ruby, this is like ||=.

And if you look at the family of x= operators there are many more, :- being the most popular that I have run across. That one substitutes a variable to a default value if the variable hasn't been set. So you use it like:

x=${1:-10}

which in English is: If $1 is unset assign 10 to x, otherwise assign to x the value of $1. In a function this has the effect of assigning a default value to a parameter. So

f() {
  typeset x
  x=${1-10}
  ...
}

is equivalent to Python:

def f(x=10): 

I think David Korn will admit that the variety of operators was probably overkill, but it is now part of the POSIX standard, Section 2.6.2 so it is probably there to stay.

Even programming languages do not have more than one.

As alluded to above, there's a misconception that these are exactly the same. It is not uncommon in programming languages to have variations of the assignment statement. I mentioned ||= in Ruby. Many languages have +=, -= and so on. In Perl if you run $x += 1 that will set $x to 1 if $x hasn't been defined before (and, shame on you, if you don't have "strict" checking enabled).

4

I think the only answer to the question "Why are there so many shell syntaxes to assign variables?" is "because they all do slightly different things."

In the shell programming language, $x or ${x} is expanded to the value of the shell variable named x. In other words, when the shell interprets a line containing $x, it replaces $x with the value of the variable:

$ x=me; filepath=/home/$x
$ echo $filepath
/home/me

The shell also implements a variety of conditional expansions, including expansions with side effects:

Conditional assignments

The assignment is conditionally made before the replacement:

  • ${x=1} sets x to 1 if it was previously unset.
  • ${x:=1} sets x to 1 if it was previously unset or set to the empty string.

Examples:

$ unset x
$ echo Hello, "${x=world}"
Hello, world
$ echo "$x"
world

$ # This sets x to the empty string
$ x=
$ echo Hello, "${x=world}"
Hello,
$ echo "$x"

$ echo Hello, "${x:=world}"
Hello, world
$ echo "$x"
world

Conditional expansions

The expansion is either the value of the variable or some fixed string. The variable is not changed.

  • ${x-1} expands to ${x} if x is set. Otherwise, expands to 1.
  • ${x:-1} expands to ${x} if x is set and not empty. Otherwise, expands to 1.
  • ${x+1} expands to 1 if x is set. Otherwise, expands to an empty string.
  • ${x:+1} expands to 1 if x is set and not empty. Otherwise, expands to an empty string.

Examples:

$ unset x
$ echo Hello, "${x-world}"
Hello, world
$ echo "$x"

$ # This sets x to the empty string
$ x=
$ echo Hello, "${x-world}"
Hello,
$ echo Hello, "${x:-world}"
Hello, world
$ echo "$x"

Conditional errors

  • ${x?error message} fails and prints the error message if x is unset.
  • ${x:?error message} fails and prints the error message if x is unset or set to the empty string.

In the case of failure, the command that the expansion occurs in is not executed. If the shell is not interactive, then the shell itself will exit.

The colon

In all of the conditional syntaxes, it can be seen that using a : makes unset variables and variables set to the empty string act in the same way. That's usually a good idea, because sometimes it is not easy to unset a variable. For example, in the local assignment syntax shown immediately above, it is easy to assign a child variable to the empty string, but not at all easy to remove a variable from the child environment:

$ export foo=Goodbye
$ foo= dash -c 'echo "${foo-Hello}", world'
, world
$ foo= dash -c 'echo "${foo:-Hello}", world'
Hello, world
$ echo "$foo"
Goodbye

Conditional expansions as commands

Sometimes it is convenient to just be able to execute a conditional expansion for its side effect, without actually using it in a command. Technically this is not possible, but you can use the special builtin command :, which does nothing:

# Equivalent to:
#  if [ -z "$x" ]; then x="default value"; fi
: "${x:=default value}"
# Equivalent to:
#   if [ $# -lt 3 ]; then
#     echo "Three arguments are required" 1>&2
#     exit 1
#   fi
: "${3?Three arguments are required.}" 

Variable assignments

The shell provides a simple syntax for assigning a value to a scalar variable:

var=value

If a command consists only of assignments, then the assignments become part of the shell environment. However, if assignments are part of a command line, coming before the utility/script name, then the assignments are applied to the child environment used by the child process:

$ foo=Hello; echo "$foo, world"
Hello, world
$ echo "$foo"
Hello
$ unset foo
$ foo=Hello echo "$foo, world"
, world
$ echo "$foo"

In the command foo=Hello echo "$foo, world", the assignment is performed in the environment passed toecho. However, the argument"$foo, world"is evaluated in the main shell environment, in which$foohas no value. Furthermore, when the child process (echo, in this case) terminates, its environment is lost, so the assignment was useless. Contrast that with the case where the child process actually uses the variable: (dashis a Posix-compatible shell; I'm just using it here for clarity.bash` would have worked the same way.)

$ unset foo
$ foo=Hello dash -c 'echo "$foo, world"'
Hello, world
$ echo "$foo"

Extended assignment syntaxes

Some shells (bash, ksh and zsh, for example) also provide the syntax

var+=value

which adds value to var. What "adds" means depends on what type of variable var is: For normal string variables, value is appended to the end of var; while for variables declared as integer, value is (mathematically) added. In ksh, zsh and some versions of bash, this assignment syntax can be used for child assignments as part of a command. Consult individual shell manuals for the details on how to declare a variable to be numeric. (It is not sufficient to simply assign a number to the variable.)

These shells also have array variables, which are assigned and appended to using the syntaxes:

array=(value1 value2 ...)
array+=(value3 ...)

The parentheses in the above assignments are syntactic; you can think of the open parenthesis as being part of the assignment token. Array assignments cannot be used as part of a simple command, since arrays cannot be exported to the child environment.

Some shells have associative arrays, which support indexed assignments. Consult individual shell manuals for details on associative arrays, including how to declare them.

Assignments in arithmetic evaluation

Within arithmetic evaluation, C-like assignment operators (=, *=, +=, etc.) are available, but they only perform arithmetic assignment. A Posix shell only has two arithmetic evaluation contexts: the arithmetic expansion syntax $((...)) and the for ((expr; expr; expr)) compound statement. (You can use the : special builtin as above to turn an arithmetic expansion into a statement.)

However, most shells also allow conditional statements ((...)) and will also use arithmetic evaluation in context requiring a number, such as array subscripts and assignment to variables declared to be integers. Details vary; consult shell manuals.

Arithmetic evaluation contexts need to be treated with caution because in an arithmetic evaluation context, variable expansions are reinterpreted as arithmetic expression, not just as integers. In some shells (bash, for example), this can be exploited with injection attacks. Best practice is to never use an untrusted variable in an arithmetic evaluation context.

  • 1
    There's also a discussion to be made about the difference between x being unset, and x being set but empty. The bottom line of it is that consistently using the : versions of the expansions above instead of using the equivalent non-: versions keeps things a lot saner / simpler to deal with. – lcd047 Jun 17 '15 at 4:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy