Testing exit values in Bash
Arabesque
<p>In Bash scripting (and shell scripting in general), we often want to check the
exit value of a command to decide an action to take after it completes, likely
for the purpose of error handling. For example, to determine whether a
particular regular expression <code>regex</code> was present somewhere in a file
<code>options</code>, we might apply <code>grep(1)</code> with its POSIX <code>-q</code> option to suppress
output and just use the exit value:</p>
<pre><code>grep -q regex options
</code></pre>
<p>An approach sometimes taken is then to test the exit value with the <code>$?</code>
parameter, using <code>if</code> to check if it’s non-zero, which is not very elegant and
a bit hard to read:</p>
<pre><code># Bad practice
grep -q regex options
if (($? > 0)); then
printf '%s\n' 'myscript: Pattern not found!' >&2
exit 1
fi
</code></pre>
<p>Because the <code>if</code> construct by design <a href="http://mywiki.wooledge.org/BashPitfalls?highlight=%28is+a+command%29#if_.5Bgrep_foo_myfile.5D">tests the exit value of commands</a>,
it’s better to test the command <em>directly</em>, making the expansion of <code>$?</code>
unnecessary:</p>
<pre><code># Better
if grep -q regex options; then
# Do nothing
:
else
printf '%s\n' 'myscript: Pattern not found!\n' >&2
exit 1
fi
</code></pre>
<p>We can precede the command to be tested with <code>!</code> to <em>negate</em> the test as well,
to prevent us having to use <code>else</code> as well:</p>
<pre><code># Best
if ! grep -q regex options; then
printf '%s\n' 'myscript: Pattern not found!' >&2
exit 1
fi
</code></pre>
<p>An alternative syntax is to use <code>&&</code> and <code>||</code> to perform <code>if</code> and <code>else</code> tests
with grouped commands between braces, but these tend to be harder to read:</p>
<pre><code># Alternative
grep -q regex options || {
printf '%s\n' 'myscript: Pattern not found!' >&2
exit 1
}
</code></pre>
<p>With this syntax, the two commands in the block are only executed if the
<code>grep(1)</code> call exits with a non-zero status. We can apply <code>&&</code> instead to
execute commands if it <em>does</em> exit with zero.</p>
<p>That syntax can be convenient for quickly short-circuiting failures in
scripts, for example due to nonexistent commands, particularly if the command
being tested already outputs its own error message. This therefore cuts the
script off if the given command fails, likely due to <code>ffmpeg(1)</code> being
unavailable on the system:</p>
<pre><code>hash ffmpeg || exit 1
</code></pre>
<p>Note that the braces for a grouped command are not needed here, as there’s
only one command to be run in case of failure, the <code>exit</code> call.</p>
<p>Calls to <code>cd</code> are another good use case here, as running a script in the wrong
directory if a call to <code>cd</code> fails could have really nasty effects:</p>
<pre><code>cd wherever || exit 1
</code></pre>
<p>In general, you’ll probably only want to test <code>$?</code> when you have <em>specific</em>
non-zero error conditions to catch. For example, if we were using the
<code>--max-delete</code> option for <code>rsync(1)</code>, we could check a call’s return value
to see whether <code>rsync(1)</code> hit the threshold for deleted file count and write
a message to a logfile appropriately:</p>
<pre><code>rsync --archive --delete --max-delete=5 source destination
if (($? == 25)); then
printf '%s\n' 'Deletion limit was reached' >"$logfile"
fi
</code></pre>
<p>It may be tempting to use the <code>errexit</code> feature in the hopes of stopping a
script as soon as it encounters any error, but there are <a href="http://mywiki.wooledge.org/BashFAQ/105">some problems with
its usage</a> that make it a bit error-prone. It’s generally more
straightforward to simply write your own error handling using the methods
above.</p>
<p>For a really thorough breakdown of dealing with conditionals in Bash, take a
look at the relevant chapter of the <a href="http://mywiki.wooledge.org/BashGuide/TestsAndConditionals">Bash Guide</a>.</p>