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 to
echo. 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.
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.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).x=1
,let x=1
,((x=1))
,read x <<< 1
,eval x=1
, and probably others.