OILS / demo / errexit-pitfalls.sh View on Github | oilshell.org

219 lines, 97 significant
1#!/usr/bin/env bash
2#
3# Prep for the "Ultimate Guide to errexit".
4#
5# Usage:
6# ./errexit-pitfalls.sh <function name>
7
8set -o nounset
9set -o pipefail
10set -o errexit
11
12die() {
13 echo "$@" >&2
14 exit 1
15}
16
17#
18# inherit_errexit (bash and Oil)
19#
20# It's confusing that command subs clear the errexit flag (but subshells
21# don't.)
22
23# This is a bash quirk
24command-sub-needs-inherit-errexit() {
25 echo $(echo 1; false; echo 2)
26
27 echo 'inherit_errexit'
28 shopt -s inherit_errexit || die "bash 4.4 required"
29
30 echo $(echo 1; false; echo 2)
31}
32
33subshell-demo() {
34 ( echo 1; false; echo 2 ) # prints 1
35}
36
37#
38# command_sub_errexit (Oil)
39#
40# It's confusing that a=$(false) is different than local a=$(false).
41
42assignment-builtin-overwrites-status() {
43 set +o errexit
44
45 a=$(false)
46 echo $? # this is 1
47
48 local b=$(false)
49 echo $? # surprisingly, it's 0!
50}
51
52oil-more-errexit() {
53 shopt -s command_sub_errexit
54
55 local b=$(false) # FAILS!
56 echo $?
57}
58
59#
60# strict_errexit (Oil)
61#
62# It's confusing that 'if myfunc', 'while/until myfunc', 'myfunc || die',
63# 'myfunc && echo OK' and '! myfunc' change errexit.
64
65myfunc() {
66 echo '--- myfunc'
67 ls /zz # should cause failure
68 echo "shouldn't get here"
69}
70
71proper-function-failure() {
72 # Proper failure
73 myfunc
74 echo "Doesn't get here"
75}
76
77# Function calls in condition cause the function to IGNORE FAILURES
78function-call-in-condition() {
79 # All 4 of these surprisingly don't fail
80
81 if myfunc; then
82 echo 'if'
83 fi
84
85 myfunc && echo '&&'
86
87 myfunc || echo 'not printed'
88
89 ! myfunc
90}
91
92proper-lastpipe-failure() {
93 { echo hi; exit 5; } | sort
94 echo "doesn't get here"
95}
96
97# Same problem for pipelines, another compound command.
98pipeline-in-conditionals() {
99 # If the above function aborts early, then this one should too.
100 if { echo hi; exit 5; } | sort; then
101 echo true
102 else
103 echo false
104 fi
105 echo bad
106}
107
108#
109# Conditional As Last Statement in Function Pitfall
110#
111# It's confusing that calling a one-line function with 'foo && echo OK' isn't
112# the same as inlining that statement (due to differing exit codes).
113
114
115# Possible strict_errexit rule:
116# Disallow && (an AndOr with && as one of the operators) unless it's in an
117# if/while/until condition.
118# This would require an extra flag for _Execute().
119# cmd_flags | ERREXIT (to avoid the stack)
120# cmd_flags | IS_CONDITION
121
122last-func() {
123 test -d nosuchdir && echo no dir
124 echo survived
125
126 set -e
127 f() { test -d nosuchdir && echo no dir; }
128 echo 'in function'
129 f
130
131 # We do NOT get here.
132 echo survived
133}
134
135
136#
137# Builtins
138#
139
140read-exit-status() {
141 set +o errexit
142
143 echo line > _tmp/line
144 read x < _tmp/line # status 0 as expected
145 echo status=$?
146
147 echo -n no-newline > _tmp/no
148 read x < _tmp/no # somewhat surprising status 1, because no delimiter read
149 # This is for terminating loops?
150
151 echo status=$?
152
153 # Solution: Oil can have its own builtin. It already has 'getline'.
154 # getfile / slurp / readall
155 # readall :x < myfile
156 #
157 # grep foo *.c | readall :results
158 #
159 # grep foo *.c | slurp :results # I Kind of like this
160 #
161 # Unlike $(echo hi), it includes the newline
162 # Unlike read x, it doesn't fail if there's NO newline.
163}
164
165
166#
167# lastpipe and SIGPIPE
168#
169
170# This is a bit of trivia about the exit status.
171sigpipe-error() {
172 set +o errexit
173 busybox | head -n 1
174 echo status=$? # 141 because of sigpipe
175
176 # Workaround
177 { busybox || true; } | head -n 1
178 echo status=$?
179}
180
181#
182# Other constructs I don't care about from https://mywiki.wooledge.org/BashFAQ/105
183#
184
185#
186# - let i++
187# - (( i++ ))
188
189# So we DO want command_sub_errexit. Because we don't want
190#
191# diff $(error 1) $(error 2)
192#
193# to execute the diff! It should fail earlier
194
195word-failures() {
196 set -o errexit
197
198 echo $BASH_VERSION
199 #shopt -s inherit_errexit
200
201 echo "command sub $(false)"
202 echo status=$?
203
204 readonly x="readonly command sub $(false)"
205 echo status=$?
206
207 [[ "dparen $(false)" == 'dparen ' ]]
208 echo status=$?
209
210 (( a = $(false)42 ))
211 echo status=$?
212 echo a=$a
213
214 diff -u <(cat nonexistent.txt) /dev/null
215 echo status=$?
216}
217
218"$@"
219