1 # Cases relevant to Oil:
2 #
3 # - shopt -s command_sub_errexit
4 # - and maybe inherit_errexit and strict_errexit (OSH)
5 #
6 # Summary:
7 # - errexit is reset to false in ash/bash -- completely ignored!
8 # - local assignment is different than global! The exit code and errexit
9 # behavior are different because the concept of the "last command" is
10 # different.
11 # - ash has copied bash behavior!
12
13 #### command sub: errexit is NOT inherited and outer shell keeps going
14
15 # This is the bash-specific bug here:
16 # https://blogs.janestreet.com/when-bash-scripts-bite/
17 # See inherit_errexit below.
18 #
19 # I remember finding a script that relies on bash's bad behavior, so OSH copies
20 # it. Instead command_sub_errexit, is recommended.
21
22 set -o errexit
23 echo $(echo one; false; echo two) # bash/ash keep going
24 echo parent status=$?
25 ## STDOUT:
26 one two
27 parent status=0
28 ## END
29 # dash and mksh: inner shell aborts, but outer one keeps going!
30 ## OK dash/mksh STDOUT:
31 one
32 parent status=0
33 ## END
34
35 #### command sub with inherit_errexit only
36 set -o errexit
37 shopt -s inherit_errexit || true
38 echo zero
39 echo $(echo one; false; echo two) # bash/ash keep going
40 echo parent status=$?
41 ## STDOUT:
42 zero
43 one
44 parent status=0
45 ## END
46 ## N-I ash STDOUT:
47 zero
48 one two
49 parent status=0
50 ## END
51
52 #### strict_errexit with command sub stops program
53 set -o errexit
54 shopt -s inherit_errexit || true
55 shopt -s strict_errexit || true
56 if echo $(echo 1; false; echo 2); then
57 echo A
58 fi
59 echo done
60
61 ## status: 1
62 ## stdout-json: ""
63
64 ## N-I bash/ash status: 0
65 ## N-I bash/ash STDOUT:
66 1 2
67 A
68 done
69 ## END
70
71 ## N-I dash/mksh status: 0
72 ## N-I dash/mksh STDOUT:
73 1
74 A
75 done
76 ## END
77
78 #### {inherit,strict}_errexit: command sub with a single command
79 set -o errexit
80 shopt -s inherit_errexit || true
81 shopt -s strict_errexit || true
82 if echo $(false); then
83 echo A
84 fi
85 echo done
86 ## status: 1
87 ## stdout-json: ""
88
89 ## N-I dash/bash/mksh/ash status: 0
90 ## N-I dash/bash/mksh/ash STDOUT:
91
92 A
93 done
94 ## END
95
96 #### strict_errexit with command sub in weird place
97 shopt -s oil:basic
98
99 if echo 1 > $(echo tmp); then
100 echo 2
101 fi
102 ## status: 1
103 ## STDOUT:
104 ## END
105 ## N-I dash/bash/mksh/ash status: 0
106 ## N-I dash/bash/mksh/ash STDOUT:
107 2
108 ## END
109
110 #### strict_errexit allows pipeline because you can set -o pipefail
111 case $SH in (dash|ash) exit ;; esac
112
113 set -o errexit
114 set -o pipefail
115 shopt -s strict_errexit || true
116
117 if echo 1 | grep 1; then
118 echo one
119 fi
120
121 #{ echo 3; exit 3; } | grep 3
122 #echo status ${PIPESTATUS[@]}
123
124 if { echo 5; exit 5; } | grep 5; then
125 echo 'should not succeed'
126 else
127 echo status ${PIPESTATUS[@]}
128 fi
129 ## STDOUT:
130 1
131 one
132 5
133 status 5 0
134 ## END
135 ## N-I dash/ash stdout-json: ""
136
137 #### command sub with command_sub_errexit only
138 set -o errexit
139 shopt -s command_sub_errexit || true
140 echo zero
141 echo $(echo one; false; echo two) # bash/ash keep going
142 echo parent status=$?
143 ## STDOUT:
144 zero
145 one two
146 parent status=0
147 ## END
148 ## N-I dash/mksh STDOUT:
149 zero
150 one
151 parent status=0
152 ## END
153
154 #### command sub with inherit_errexit and command_sub_errexit
155 set -o errexit
156
157 # bash implements inherit_errexit, but it's not as strict as OSH.
158 shopt -s inherit_errexit || true
159 shopt -s command_sub_errexit || true
160 echo zero
161 echo $(echo one; false; echo two) # bash/ash keep going
162 echo parent status=$?
163 ## STDOUT:
164 zero
165 ## END
166 ## status: 1
167 ## N-I dash/mksh/bash status: 0
168 ## N-I dash/mksh/bash STDOUT:
169 zero
170 one
171 parent status=0
172 ## END
173 ## N-I ash status: 0
174 ## N-I ash STDOUT:
175 zero
176 one two
177 parent status=0
178 ## END
179
180 #### command sub: last command fails but keeps going and exit code is 0
181 set -o errexit
182 echo $(echo one; false) # we lost the exit code
183 echo status=$?
184 ## STDOUT:
185 one
186 status=0
187 ## END
188
189 #### global assignment with command sub: middle command fails
190 set -o errexit
191 s=$(echo one; false; echo two;)
192 echo "$s"
193 ## status: 0
194 ## STDOUT:
195 one
196 two
197 ## END
198 # dash and mksh: whole thing aborts!
199 ## OK dash/mksh stdout-json: ""
200 ## OK dash/mksh status: 1
201
202 #### global assignment with command sub: last command fails and it aborts
203 set -o errexit
204 s=$(echo one; false)
205 echo status=$?
206 ## stdout-json: ""
207 ## status: 1
208
209 #### local: middle command fails and keeps going
210 set -o errexit
211 f() {
212 echo good
213 local x=$(echo one; false; echo two)
214 echo status=$?
215 echo $x
216 }
217 f
218 ## STDOUT:
219 good
220 status=0
221 one two
222 ## END
223 # for dash and mksh, the INNER shell aborts, but the outer one keeps going!
224 ## OK dash/mksh STDOUT:
225 good
226 status=0
227 one
228 ## END
229
230 #### local: last command fails and also keeps going
231 set -o errexit
232 f() {
233 echo good
234 local x=$(echo one; false)
235 echo status=$?
236 echo $x
237 }
238 f
239 ## STDOUT:
240 good
241 status=0
242 one
243 ## END
244
245 #### local and inherit_errexit / command_sub_errexit
246 # I've run into this problem a lot.
247 set -o errexit
248 shopt -s inherit_errexit || true # bash option
249 shopt -s command_sub_errexit || true # oil option
250 f() {
251 echo good
252 local x=$(echo one; false; echo two)
253 echo status=$?
254 echo $x
255 }
256 f
257 ## status: 1
258 ## STDOUT:
259 good
260 ## END
261 ## N-I ash status: 0
262 ## N-I ash STDOUT:
263 good
264 status=0
265 one two
266 ## END
267 ## N-I bash/dash/mksh status: 0
268 ## N-I bash/dash/mksh STDOUT:
269 good
270 status=0
271 one
272 ## END
273
274 #### global assignment when last status is failure
275 # this is a bug I introduced
276 set -o errexit
277 [ -n "${BUILDDIR+x}" ] && _BUILDDIR=$BUILDDIR
278 BUILDDIR=${_BUILDDIR-$BUILDDIR}
279 echo status=$?
280 ## STDOUT:
281 status=0
282 ## END
283
284 #### global assignment when last status is failure
285 # this is a bug I introduced
286 set -o errexit
287 x=$(false) || true # from abuild
288 [ -n "$APORTSDIR" ] && true
289 BUILDDIR=${_BUILDDIR-$BUILDDIR}
290 echo status=$?
291 ## STDOUT:
292 status=0
293 ## END
294
295 #### strict_errexit prevents errexit from being disabled in function
296 set -o errexit
297 fun() { echo fun; }
298
299 fun || true # this is OK
300
301 shopt -s strict_errexit || true
302
303 echo 'builtin ok' || true
304 env echo 'external ok' || true
305
306 fun || true # this fails
307
308 ## status: 1
309 ## STDOUT:
310 fun
311 builtin ok
312 external ok
313 ## END
314 ## N-I dash/bash/mksh/ash status: 0
315 ## N-I dash/bash/mksh/ash STDOUT:
316 fun
317 builtin ok
318 external ok
319 fun
320 ## END
321
322 #### strict_errexit prevents errexit from being disabled in brace group
323 set -o errexit
324 # false failure is NOT respected either way
325 { echo foo; false; echo bar; } || echo "failed"
326
327 shopt -s strict_errexit || true
328 { echo foo; false; echo bar; } || echo "failed"
329 ## status: 1
330 ## STDOUT:
331 foo
332 bar
333 ## END
334
335 ## N-I dash/bash/mksh/ash status: 0
336 ## N-I dash/bash/mksh/ash STDOUT:
337 foo
338 bar
339 foo
340 bar
341 ## END
342
343 #### strict_errexit prevents errexit from being disabled in subshell
344 set -o errexit
345 shopt -s inherit_errexit || true
346
347 # false failure is NOT respected either way
348 ( echo foo; false; echo bar; ) || echo "failed"
349
350 shopt -s strict_errexit || true
351 ( echo foo; false; echo bar; ) || echo "failed"
352 ## status: 1
353 ## STDOUT:
354 foo
355 bar
356 ## END
357
358 ## N-I dash/bash/mksh/ash status: 0
359 ## N-I dash/bash/mksh/ash STDOUT:
360 foo
361 bar
362 foo
363 bar
364 ## END
365
366 #### strict_errexit and ! && || if while until
367 prelude='set -o errexit
368 shopt -s strict_errexit || true
369 fun() { echo fun; }'
370
371 $SH -c "$prelude; ! fun; echo 'should not get here'"
372 echo bang=$?
373 echo --
374
375 $SH -c "$prelude; fun || true"
376 echo or=$?
377 echo --
378
379 $SH -c "$prelude; fun && true"
380 echo and=$?
381 echo --
382
383 $SH -c "$prelude; if fun; then true; fi"
384 echo if=$?
385 echo --
386
387 $SH -c "$prelude; while fun; do echo while; exit; done"
388 echo while=$?
389 echo --
390
391 $SH -c "$prelude; until fun; do echo until; exit; done"
392 echo until=$?
393 echo --
394
395
396 ## STDOUT:
397 bang=1
398 --
399 or=1
400 --
401 and=1
402 --
403 if=1
404 --
405 while=1
406 --
407 until=1
408 --
409 ## END
410 ## N-I dash/bash/mksh/ash STDOUT:
411 fun
412 should not get here
413 bang=0
414 --
415 fun
416 or=0
417 --
418 fun
419 and=0
420 --
421 fun
422 if=0
423 --
424 fun
425 while
426 while=0
427 --
428 fun
429 until=0
430 --
431 ## END
432
433 #### if pipeline doesn't fail fatally
434 set -o errexit
435 set -o pipefail
436
437 f() {
438 local dir=$1
439 if ls $dir | grep ''; then
440 echo foo
441 echo ${PIPESTATUS[@]}
442 fi
443 }
444 rmdir $TMP/_tmp || true
445 rm -f $TMP/*
446 f $TMP
447 f /nonexistent # should fail
448 echo done
449
450 ## N-I dash status: 2
451 ## N-I dash stdout-json: ""
452 ## STDOUT:
453 done
454 ## END
455
456 #### errexit is silent (verbose_errexit for Oil)
457 shopt -u verbose_errexit 2>/dev/null || true
458 set -e
459 false
460 ## stderr-json: ""
461 ## status: 1
462
463 #### command sub errexit preserves exit code
464 set -e
465 shopt -s command_sub_errexit || true
466
467 echo before
468 echo $(exit 42)
469 echo after
470 ## STDOUT:
471 before
472 ## END
473 ## status: 42
474 ## N-I dash/bash/mksh/ash STDOUT:
475 before
476
477 after
478 ## N-I dash/bash/mksh/ash status: 0
479
480 #### strict_errexit without errexit
481 myproc() {
482 echo myproc
483 }
484 myproc || true
485
486 # This should be a no-op I guess
487 shopt -s strict_errexit || true
488 myproc || true
489
490 ## STDOUT:
491 myproc
492 myproc
493 ## END
494
495
496 #### What's in strict:all?
497
498 # inherit_errexit, strict_errexit, but not command_sub_errexit!
499 # for that you need oil:basic!
500
501 set -o errexit
502 shopt -s strict:all || true
503
504 # inherit_errexit is bash compatible, so we have it
505 #echo $(date %x)
506
507 # command_sub_errexit would hide errors!
508 f() {
509 local d=$(date %x)
510 }
511 f
512
513 deploy_func() {
514 echo one
515 false
516 echo two
517 }
518
519 if ! deploy_func; then
520 echo failed
521 fi
522
523 echo 'should not get here'
524
525 ## status: 1
526 ## STDOUT:
527 ## END
528 ## N-I dash/bash/mksh/ash status: 0
529 ## N-I dash/bash/mksh/ash STDOUT:
530 one
531 two
532 should not get here
533 ## END
534
535 #### command_sub_errexit causes local d=$(date %x) to fail
536 set -o errexit
537 shopt -s inherit_errexit || true
538 #shopt -s strict_errexit || true
539 shopt -s command_sub_errexit || true
540
541 myproc() {
542 # this is disallowed because we want a runtime error 100% of the time
543 local x=$(true)
544
545 # Realistic example. Should fail here but shells don't!
546 local d=$(date %x)
547 echo hi
548 }
549 myproc
550
551 ## status: 1
552 ## STDOUT:
553 ## END
554 ## N-I dash/bash/mksh/ash status: 0
555 ## N-I dash/bash/mksh/ash STDOUT:
556 hi
557 ## END
558
559 #### command_sub_errexit and command sub in array
560 case $SH in (dash|ash|mksh) exit ;; esac
561
562 set -o errexit
563 shopt -s inherit_errexit || true
564 #shopt -s strict_errexit || true
565 shopt -s command_sub_errexit || true
566
567 # We don't want silent failure here
568 readonly -a myarray=( one "$(date %x)" two )
569
570 #echo len=${#myarray[@]}
571 argv.py "${myarray[@]}"
572 ## status: 1
573 ## STDOUT:
574 ## END
575 ## N-I bash status: 0
576 ## N-I bash STDOUT:
577 ['one', '', 'two']
578 ## END
579 ## N-I dash/ash/mksh status: 0
580