Darren Mulholland

Bash Braces Cheatsheet

A no-frills guide to the Bash shell's many, many styles of braces.


•   (...)

Single parentheses indicate a subshell. A list of commands enclosed in parentheses will be executed in a separate shell process. The exit status of the subshell is the exit status of the command list.

$ (echo foo; exit 42)
foo
$ echo $?
42

•   $(...)

Single parentheses preceded by a dollar symbol indicate command substitution. The command inside the parentheses is run in a subshell and its standard output, stripped of any trailing newlines, is returned as the value of the expression.

$ foo=$(echo bar)
$ echo $foo
bar

•   ((...))

Double parentheses indicate an arithmetic expression. Inside the parentheses, variables are interpreted as integer values and a selection of arithmethic and boolean operators are supported

Somewhat confusingly, the exit status of the expression is 0 (success) if the arithmetic value of the expression is non-zero; the exit status of the expression is 1 (failure) if the arithmetic value of the expression is zero.

$ ((1 + 1))
$ echo $?
0

$ ((1 - 1))
$ echo $?
1

Note that within the expression itself boolean operators return a value of 1 for true and 0 for false.

$ ((2 > 1))
$ echo $?
0

$ ((2 < 1))
$ echo $?
1

•   $((...))

Double parentheses preceded by a dollar symbol indicate arithmetic expansion. The value of the expansion is the integer value of the arithmetic expression in the parentheses.

$ echo $((1 + 1))
2

$ echo $((2 > 1))
1

•   { ... }

Curly braces indicate a command block, a construct which feels a lot like a solution in search of a problem. Personally, I've only ever used them for writing condensed one-line conditionals:

[ condition ] && { command1; command2; }

Like functions, command blocks support redirected I/O.

{ cmd1; cmd2; } < infile > outfile

Unlike functions, they don't support local variables; all variables inside a command block are global.

Note that a syntex quirk of command blocks is that they require surrounding spaces and a trailing semicolon before closing. (Newlines also work.)

•   ${...}

Curly braces preceded by a dollar symbol indicate parameter expansion${param} is a less ambiguous way of writing $param and both expand to the value of the variable param.

$ foo=bar
$ echo ${foo}
bar

Parameter expansions support a host of impossible-to-remember string operator sigils.

•   [ ... ]

Single brackets indicate a conditional expression. This construct is an alias for the builtin test command and returns a exit status of 0 or 1 depending on the boolean value of the expression.

The behaviour of test depends on the number of arguments supplied to it. If there are zero arguments, the expression is false. If there is one argument, the expression is true if and only if the argument is not null.

$ [ ] && echo true || echo false
false

$ [ "" ] && echo true || echo false
false

$ [ "foo" ] && echo true || echo false
true

With two or more arguments, test supports a large selection of boolean flags and operators (see the manual for a full listing).

•   [[ ... ]]

Double brackets indicate a more advanced form of conditional expression. Unlike test which is an ordinary command, the [[ ... ]] construct is part of bash's own syntax and so can support a richer set of operators.

In particular, double bracket conditionals support the short-circuiting && and || operators, along with >, <, >=, <=, !=, == string comparisons and regex-based pattern matching.

To take an explicit example, the following single-bracket conditional will throw a syntax error:

if [ -e $file1 && -e $file2 ]; then echo true; fi

The && operator is part of Bash's syntax and cannot be used as an argument to the [ command.

We could rewrite the conditional using the test command's own -a operator:

if [ -e $file1 -a -e $file2 ]; then echo true; fi

Alternatively, we could break the conditional into two separate tests:

if [ -e $file1 ] && [ -e $file2 ]; then echo true; fi

Finally, we could rewrite the conditional using double brackets which do support the && operator:

if [[ -e $file1 && -e $file2 ]]; then echo true; fi