Warning: Work in progress! Leave feedback on Zulip or Github if you'd like this doc to be updated.

Error Handling With set -e / errexit

Unix shell programmers disagree on what the best way to handle errors is. The set -e mechanism is unreliable, and that can make your programs unreliable.

A primary goal of Oil is: Don't give anyone an excuse not to use set -e.

This doc explains how we accomplish this, but most users don't need to know the details. All you need to do is to follow the instructions in Oil Options.

That is, add shopt --set oil:basic to the top of your program, or use bin/oil instead of bin/osh.

The basic guarantee that Oil gives you is that it doesn't lose errors. Your program won't chug along in a bad state for hours after rm fails.

Table of Contents
Overview
What Mechanisms Does Oil Provide?
Background
The Fundmental Problem
Problem
Solution in Shell
Solution Oil
Style Guide
Links
OSH Has Four errexit Options (while Bash Has Two)
Quirk 1: the Shell Sometimes Disables And Restores errexit
Quirk 2: x=$(false) is inconsitent with local x=$(false)
Additional errexit options
Example
Recap/Summary
Related Documents

Overview

TODO: Transcribe Reliable Error Handling.

We have:

What Mechanisms Does Oil Provide?

TODO: organize this.

These are new in Oil:

  1. strict_errexit: Disallow programming patterns that would lead to ignored errors.
  2. command_sub_errexit: Check for failure at the end of command subs, like local d=$(date %x).

Background

Here's some background knowledge on Unix shell, which will motivate the improvements in Oil.

(1) Shell has a global variable $? that stores the integer status of the last command. For example:

(2) "Bare" Assignments are considered commands.

(3) Assignment builtins have their own exit status.

Surprisingly, the exit status of local a=$(false) is 0, not 1. That is, simply adding local changes the exit status of an assignment.

The local builtin has its own status which overwrites $?, and you lose the status of false.

(4) You can explicitly check $? for failure, e.g. test $? -eq 0 || die "fail".

The set -e / set -o errexit mechanism tries to automate these checks, but it does it in a surprising way.

This rule causes a lot of problems:

The -e setting shall be ignored when executing the compound list following the while, until, if, or elif reserved word, a pipeline beginning with the ! reserved word, or any command of an AND-OR list other than the last.

(5) Bash implement errexit differently than other shells. `shopt -s More details:

The Fundmental Problem

Problem

Solution in Shell

set +o errexit

my-complex-func
status=$?

other-func
status=$?

set -o errexit

Solution Oil

shopt -u errexit {
  var status = 0 

  my-complex-func
  setvar status = $?

  other-func
  setvar status = $?
}

Style Guide

No:

if myfunc ...             # internal exit codes would be thrown away

if ls | wc -l ;           # first exit code would be thrown away

Yes:

if external-command ...   # e.g. grep
if builtin ...            # e.g. test
if $0 myfunc ...          # $0 pattern

The $0 myfunc pattern wraps the function in an external command.

Links

OSH Has Four errexit Options (while Bash Has Two)

The complex behavior of these global execution options requires extra attention in this manual.

But you don't need to understand all the details. Simply choose between:

# Turn on four errexit options.  I don't run this script with other shells.
shopt -s oil:all

and

# Turn on three errexit options.  I run this script with other shells.
shopt -s strict:all

Quirk 1: the Shell Sometimes Disables And Restores errexit

Here's some background for understanding the additional errexit options described below.

In all Unix shells, the errexit check is disabled in these situations:

  1. The condition of the if, while, and until constructs
  2. A command/pipeline prefixed by !
  3. Every clause in || and && except the last.

Now consider this situation:

  1. errexit is on
  2. The shell disables it one of those three situations
  3. While disabled, the user touches it with set -o errexit (or +o to turn it off).

Surprising behavior: Unix shells ignore the set builtin for awhile, delaying its execution until after the temporary disablement.

Quirk 2: x=$(false) is inconsitent with local x=$(false)

Background: In shell, local is a builtin rather than a keyword, which means local foo=$(false) behaves differently than than foo=$(false).

Additional errexit options

OSH aims to fix the many quirks of errexit. It has this bash-compatible option:

And two more options:

Example

When both inherit_errexit and command_sub_errexit are on, this code

echo 0; echo $(touch one; false; touch two); echo 3

will print 0 and touch the file one.

  1. The command sub aborts at false (`inherrit_errexit), and
  2. The parent process aborts after the command sub fails (command_sub_errexit).

Recap/Summary

Good articles on errexit:

Related Documents


Generated on Sat Jan 23 00:06:31 PST 2021