Eli Korvigo
Eli Korvigo

Reputation: 10483

Bash: usage of `true`

In many scripts I've inherited from a former employee I keep seeing this pattern:

if (true $SOME_VAR)&>/dev/null; then
    ...
fi

or this one

(true $SOME_VAR)&>/dev/null || SOME_VAR="..."

The man page for true says it always returns true, hence I keep wondering, what is the point of these checks? In the first case the then part is always executed, in the second case the right hand part is never executed.

Upvotes: 21

Views: 1464

Answers (4)

mklement0
mklement0

Reputation: 437090

To complement jwodder's helpful answer and Fred's helpful answer:

  • In Bash v4.2+ , the less obscure and more efficient -v operator can be used to test if a variable is defined[1] (note that no $ must be used):
    [[ -v SOME_VAR ]]

  • In older Bash versions and in POSIX-compliant scripts, use Fred's parameter-expansion-based approach, which is also more efficient than the (true ...) approach.

  • If the intent is to simply provide a default value, as in the (true $SOME_VAR)&>/dev/null || SOME_VAR="..." idiom, use the (POSIX-compliant) technique suggested by kojiro, also based on a parameter expansion:
    SOME_VAR=${SOME_VAR-...} # keep $SOME_VAR value or default to '...'
    Toby Speight suggests another POSIX-compliant variant, ${SOME_VAR=...}, which directly updates the variable with the default value, if it is undefined; however, it has the side effect of expanding to the (resulting) value - which may or may not be desired. A concise, but also slightly obscure way to suppress the expansion is to pass the expansion to the colon (null) utility (:), which expands, but otherwise ignores its arguments (compared to using true for the same purpose, it is perhaps slightly less confusing):
    : ${SOME_VAR=...} # set $SOMEVAR to '...' only if not defined

  • Note that all parameter expansions shown/mentioned above have a variant that places : before the operator, which then acts not only when the variable is undefined, but also when it is defined but empty (contains the null string):
    ${SOME_VAR:+...}, ${SOME_VAR:-...}, ${SOME_VAR:=...}
    Arguably, this variant behavior is the generally more robust technique, especially given that when set -u (set -o nunset) is not turned on, undefined variables expand to the null (empty) string.

To add to jwodder's explanation:

  • The use of (...) around true $SOME_VAR to create a subshell is crucial for this somewhat obscure test for variable existence to work as intended.

    • Without a subshell, the entire script would abort.

    • The need for a subshell makes the technique not just obscure, but also inefficient (although that won't really be noticeable with occasional use).

      • Additionally, if set -u (set -o nounset) happens not to be in effect, the technique treats all variables as defined.
    • With the subshell, only the subshell aborts, which is reflected in its exit code to the current shell: 1, if the subshell aborted (the variable doesn't exist), 0 otherwise.
      Therefore, the (true ...) command only evaluates to (conceptually) true if the variable exists.

    • &>/dev/null suppresses the error message from the subshell that is emitted if the variable doesn't exist.

      • As an aside: true never produces no output, so it is sufficient to use (true $SOME_VAR)2>/dev/null (suppress stderr only) - this change makes the technique POSIX-compliant (though still not advisable).
  • It isn't just set -u (set -o nounset) statements inside a script that turn on aborting in case of access to an undefined variable - invoking bash explicitly with command-line option -u has the same effect.


[1] Since Bash v4.3, you can also test whether an array variable has an element with the specified index; e.g.:
a=( one two ); [[ -v a[0] ]] succeeds, because an array element with index 0 exists; works analogously with associative arrays.

Upvotes: 14

Joshua
Joshua

Reputation: 43188

While not strictly the same,

if [ x"$SOME_VAR" = x ]; then
    ...
fi

tends to do what you want; that is the if is true if $SOME_VAR is undefined or (difference:) defined to be the zero-length string.

This code does not work if SOME_VAR is unset and -u is set. I believe the following bashism works though: "${SOME_VAR-}" = "".

Upvotes: 3

Fred
Fred

Reputation: 6995

The following is probably equivalent, and more straightforward :

if [ "${SOME_VAR+x}" ] then
    ...
fi

Or, in the assignment case :

[ "${SOME_VAR+x}" ] || SOME_VAR="..."

The + expansion operator expands to a null string if the variable is unset, and to x if it is assigned (assigned a null string still means assigned). In this case, you could replace x by whatever you want (except a null string).

There is also a ${SOME_VAR:+x} variant. The difference is with null strings : :+ expands to a null string if the variable is assigned a null string (while + expands to x if the value is assigned, even if it is a null string).

Upvotes: 6

jwodder
jwodder

Reputation: 57460

If set -u (a.k.a. set -o nounset) is in effect, true $SOME_VAR will fail when $SOME_VAR is not defined. This is therefore a way to test whether the variable is defined.

Upvotes: 24

Related Questions