piss

entries

  1. Testing exit values in Bash
    Arabesque 2013-10-28T05:56:37+00:00
  2. Prompt directory shortening
    Arabesque 2014-11-07T09:13:47+00:00
  3. Shell config subfiles
    Arabesque 2015-01-29T11:01:09+00:00
  4. Cron best practices
    Arabesque 2016-05-08T05:19:19+00:00
  5. Custom commands
    Arabesque 2016-10-22T05:37:44+00:00
  6. Bash hostname completion
    Arabesque 2017-02-10T10:32:17+00:00
  7. Shell from vi
    Arabesque 2017-02-18T10:46:56+00:00
  8. Vimways: From .vimrc to .vim
    Arabesque 2018-12-08T08:50:17+00:00
  9. Vimways: Runtime hackery
    Arabesque 2018-12-10T21:27:24+00:00
  10. Passing runtime data to AWK
    Arabesque 2020-05-31T11:55:54+00:00

Testing exit values in Bash

Arabesque

source

<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&#8217;s non-zero, which is not very elegant and a bit hard to read:</p> <pre><code># Bad practice grep -q regex options if (($? &gt; 0)); then printf '%s\n' 'myscript: Pattern not found!' &gt;&amp;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&#8217;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' &gt;&amp;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!' &gt;&amp;2 exit 1 fi </code></pre> <p>An alternative syntax is to use <code>&amp;&amp;</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!' &gt;&amp;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>&amp;&amp;</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&#8217;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&#8217;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&#8217;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' &gt;"$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&#8217;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>