Shell usage of ":-" and advantage

It’s more streamlined; you can do something in one simple command rather than two (for example, [[ -z $var ]] && var=/temp) or a complex command like

if [[ -z $var ]]
then
    var=/temp
fi

Also, it doesn’t require that the conditional value be assigned to a variable.  For example,

#!/bin/sh
prog="$1"
# Do sanity checks & input validation here.
cc "$prog" -o "${2:-$(basename "$prog")}"

If this is invoked as comp foo.c, it will compile foo.c and write the compiled output to foo.  But comp foo.c fu will compile foo.c and write the compiled output to fu.

This becomes especially significant if you want to use multiple parameters with default values:

some_command ${1:-default_value1} ${2:-default_value2} ${3:-default_value3} ${4:-default_value4} ${5:-default_value5} ${6:-default_value6} ${7:-default_value7} ${8:-default_value8} ${9:-default_value9}

Doing this the other way would require some 18 commands to set up the parameters.

Another example of how to use this where you don’t need to assign the conditional value to a variable is a script that wants to let the user edit a file:

#!/bin/sh
    ⋮ 
file=something
    ⋮ 
"$(EDITOR:-vi}" "$file"

which invokes the user’s favorite editor (as specified through the EDITOR environment variable), or vi if the user hasn’t specified one.


It's a compact idiom. You can use it inline where commands are executed:

cmd "${arg1}" "${arg2}" "${arg3:-42}"

or at the start of the program (or as first statement in functions) to assert the parameter interface (using the : command):

: "${1:?} ${2:?}"

or check and assign it to the semantically named variables on which you operate in the program or resp. function:

foo=${1:?} bar=${2:?} baz=${3:-42}

where the first two arguments (in this example) are mandatory and the third one has a default value (above it's "42") if not provided.

You can also use the related constructs ${var:=val} (or resp. ${var=val}) for that purpose if not positional parameters but variables are involved:

: "${foo:=${1:?}} ${bar:=${2:?}} ${baz:=${3:-42}}"

The ${var:-42} idiom has also a variant ${var-42} (i.e. without the : colon) to differentiate unset parameters from empty parameters; test constructs can not directly differentiate empty variables from unset variables.


There are three cases: var might have been initially unset, empty, or non-empty. In the first two cases, it's set to /temp; in the last case it's left alone.

Another way to do the same thing is

: "${var:=/temp}"

I prefer that one because the chain of assignments is clearer, but it's a matter of aesthetics.

Under normal settings, this is equivalent to

if [ -z "$var" ]; then var="/temp"; fi

which is arguably clearer. However, the syntax ${var:-…} or ${var:=…} has an advantage: it works even under set -u, which causes $var to error out if var is unset. The advantage of set -u is that it makes it easy to catch mistakes due to mistyping the name of a variable or forgetting to define it under certain conditions. However, it can only be used with scripts that don't assume that expanding an unset variable is always possible.

Tags:

Shell

Ksh