Skip to content

Commit a48c0e1

Browse files
author
Craig Ringer
committed
bash error template
1 parent 46ed93b commit a48c0e1

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed

bash_error_template/README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# bash error template
2+
3+
This is a skeleton for an error handler that can be sourced from your random
4+
bash scripts to give you saner error handling and a sort of weak "strict mode"
5+
for bash.
6+
7+
Now don't use it, use a real language instead.

bash_error_template/error_template.sh

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
#!/bin/bash
2+
3+
#
4+
# -e: Trap when any command returns nonzero (failed) and exit.
5+
# -u: Trap and exit with an error if any undefined variable is referenced
6+
# -E: Inherit traps within functions
7+
#
8+
# -o pipefail: If any part of a pipe fails, the pipe as a whole returns a
9+
# the failure code of the first failing part of the pipe. Without this
10+
# a pipe succeeds if the last command succeeds.
11+
#
12+
#
13+
# Scripting for -e mode
14+
# ----------------------
15+
# When running with -e, make sure to handle any commands you expect to return
16+
# nonzero with one of these patterns:
17+
#
18+
# if command args
19+
# then
20+
# : # do nothing or on-success actions here
21+
# else
22+
# ecode=$?
23+
# ... handle error here ...
24+
# fi
25+
#
26+
#
27+
# if cmdoutput=$(command args)
28+
# then
29+
# ... do what to do if it worked ...
30+
# else
31+
# ecode=$?
32+
# ... do what to do if it failed...
33+
# fi
34+
#
35+
#
36+
# ecode=0
37+
# command args || ecode=$?
38+
# if [[ "$ecode" -ne 0 ]]
39+
# then
40+
# ... handle error here ...
41+
# fi
42+
#
43+
# Scripting for -u mode
44+
# ---------------------
45+
#
46+
# If running with -u, you can't just use $1 if $1 may not be defined, etc. If a
47+
# var may not be defined you have to conditionally access it.
48+
#
49+
# Typical patterns:
50+
#
51+
# ${var:-"default here"}
52+
#
53+
# ${var:?"error message if undefined"}
54+
#
55+
# ${var:+"only emitted if $var is defined"}
56+
#
57+
# if [[ -n "${var:-}" ]]
58+
# then
59+
# ... $var defined ...
60+
# fi
61+
#
62+
# You should generally pre-declare any globals.
63+
#
64+
# Take particular care with arrays. You cannot "${arrayvar[@]}" a possibly-undefined
65+
# array. You have to use this icky spelling:
66+
#
67+
# "${#arrayvar[@]:+"${arrayvar[@]"}"
68+
#
69+
#
70+
# Function variable scopes
71+
# ------------------------
72+
#
73+
# Make a habit of using "local" in your functions to protect variable scopes. E.g. in this
74+
# exmple, "$arg" won't be defined outside of function foo():
75+
#
76+
# function foo() {
77+
# local arg="${1:-default}"
78+
# do_something_with $arg
79+
# }
80+
# foo "${global}"
81+
#
82+
# To return multiple values from functions and return efficiently, use output globals.
83+
# It's gross, but so is bash. E.g.:
84+
#
85+
# function bar() {
86+
# local x="${1:-}"
87+
# bar_out="$(( $x ++ ))"
88+
# }
89+
# bar 1
90+
# echo ${bar_out}
91+
#
92+
# While we are here
93+
# -----------------
94+
#
95+
# You can use 'printf' to set dynamic output variables.
96+
#
97+
# printf -v $VARNAME "whatever value"
98+
#
99+
# and "${!VARNAME}" to access them by name-reference.
100+
#
101+
# But it's better to use "declare -A" and associative arrays where possible,
102+
# since they're less awful than dynamic variable names.
103+
#
104+
# If you want to preserve spaces and quoting while building up a command
105+
# use an array and "${x[@]}" expansion, e.g.
106+
#
107+
# declare -a cmd_and_args=("cmd" "arg1" "arg 2 kept together")
108+
# cmd_and_args+=("arg 3 is not split by spaces")
109+
# # run it, preserving quoting and arg groupings
110+
# "${cmd_and_args[@]}"
111+
#
112+
# The 'read' command is great for simply splitting up input, and can
113+
# be used with a here-string "<<<".
114+
#
115+
# read -r VAR1 VAR2 VAR3 <<<"var1input var2input var3input"
116+
#
117+
# It respects IFS so you can use it to split on tabs etc.
118+
#
119+
120+
121+
122+
123+
set -e -u -E -o pipefail
124+
125+
if [ -n "${DEBUG:-}" ]
126+
then
127+
set -x -o functrace
128+
fi
129+
130+
# Backtrace an error and then exit with the the original exit code
131+
#
132+
function trap_err() {
133+
filename=${BASH_SOURCE[1]:+"${BASH_SOURCE[1]}"}
134+
fileline=${BASH_LINENO[$lineno]:+"${BASH_LINENO[$lineno]}"}
135+
echo 1>&2 "ERROR: unexpected exit code $ecode at $filename:$lineno"
136+
echo "Backtrace is:"
137+
i=0
138+
while btline=$(caller $i)
139+
do
140+
if ! read -r line sub file <<<"$btline"
141+
then
142+
echo " $btline"
143+
elif [[ "$sub" == "main" || "${sub}" == "" ]]
144+
then
145+
echo " $file:$line"
146+
else
147+
echo " $file:$line $sub()"
148+
fi
149+
i=$((i+1))
150+
done
151+
exit $ecode
152+
}
153+
154+
# This trap runs on anything that would cause an exit due to -e
155+
#
156+
# It does not run on "exit n" for nonzero exit, or on -u traps
157+
# or other internal shell errors.
158+
#
159+
# If you want that, you can add another trap on EXIT.
160+
#
161+
trap 'ecode=$?;lineno=$LINENO;set +x;trap_err' ERR
162+
163+
# Example of an exit trap.
164+
#
165+
# If you want to use this you must also put a "trap EXIT" before all your
166+
# intentional exits, e.g.
167+
#
168+
# trap EXIT
169+
# exit 1
170+
#
171+
trap 'ecode=$?;lineno=$LINENO;set +x;trap EXIT;trap_err' EXIT
172+
173+
# For testing. Source this file, then call foo() in the other file, maybe
174+
# within a few nested functions, or maybe via sourcing some other files. also
175+
# try ref'ing an undefined var
176+
#
177+
#function foo() {
178+
# false
179+
#}

0 commit comments

Comments
 (0)