source | all docs for version 0.9.9 | all versions | oilshell.org
Warning: Work in progress! Leave feedback on Zulip or Github if you'd like this doc to be updated.
set -e / errexitUnix 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.
TODO: Transcribe Reliable Error Handling.
We have:
strict_errexit, command_errexit, process_sub_fail_process_sub_status, analogous to PIPESTATUSrun builtin, which accepts --status-ok, --allow-status-01, and
--assign-status.TODO: organize this.
strict:all:
errexit, pipefail -- POSIX sh stuffinherit_errexit -- bash thingstrict_errexit -- Oil thingoil:basic
process_sub_fail -- analogous to pipefail. If the status was otherwise zero,
and a process sub within the command exited nonzero, set the status of the
command to one of those values. Then errexit will make the whole command
fail.
command_sub_errexit and
process_sub_fail options don't work the same way.These are new in Oil:
strict_errexit: Disallow programming patterns that would lead to ignored
errors.command_sub_errexit: Check for failure at the end of command subs, like
local d=$(date %x).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:
echo hi returns 0ls /zzz will return an error like 2nonexistent-command returns 127(2) "Bare" Assignments are considered commands.
a=b is 0.a=$(false) is 1, because false returned 1.(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:
errexit differently.ls | grep foo, the variable $? is set to the exit status of grep.
set -o pipefail, then the status of a pipeline is the maximum of any
status.success/fail of a command and logical true/false are conflated in shell.
Oil has commands and expressions. Commands have a status but expressions don't.
TODO:
set +o errexit
my-complex-func
status=$?
other-func
status=$?
set -o errexit
shopt -u errexit {
var status = 0
my-complex-func
setvar status = $?
other-func
setvar status = $?
}
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.
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
errexitHere's some background for understanding the additional errexit options
described below.
In all Unix shells, the errexit check is disabled in these situations:
if, while, and until constructs!|| and && except the last.Now consider this situation:
errexit is onset -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.
Background: In shell, local is a builtin rather than a keyword, which means
local foo=$(false) behaves differently than than foo=$(false).
errexit optionsOSH aims to fix the many quirks of errexit. It has this bash-compatible
option:
inherit_errexit: errexit is inherited inside $(), so errors aren't
ignored. It's enabled by both strict:all and oil:all.And two more options:
strict_errexit makes the quirk above irrelevant. Compound commands,
including functions, can't be used in any of those three situations. You
can write set -o errexit || true, but not { set -o errexit; false } || true. When this option is set, you get a runtime error indicating that you
should change your code. Consider using the ["at-splice
pattern"][at-splice] to fix this, e.g. $0 myfunc || echo errexit.command_sub_errexit: Check more often for non-zero status. In particular, the
failure of a command sub can abort the entire script. For example, local foo=$(false) is a fatal runtime error rather than a silent success.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.
false (inherit_errexit), andcommand_sub_errexit).errexit -- abort the shell script when a command exits nonzero, except in
the three situations described above.inherit_errexit -- A bash option that OSH borrows.strict_errexit -- Turned on with strict:all.command_sub_errexit -- Turned on with oil:all.Good articles on errexit:
strict_errexit problems.