1 # Oil mechanisms:
2 #
3 # - shopt -s strict_errexit
4 # - shopt -s command_sub_errexit
5 # - inherit_errexit (bash)
6 #
7 # Summary:
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. But you can opt in to better behavior.
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 and assignment builtins (local, export, readonly ...)
53 set -o errexit
54 shopt -s strict_errexit || true
55 #shopt -s command_sub_errexit || true
56
57 f() {
58 local x=$(echo hi; false)
59 echo x=$x
60 }
61
62 eval 'f'
63 echo ---
64
65 ## status: 1
66 ## STDOUT:
67 ## END
68 ## N-I dash/bash/mksh/ash status: 0
69 ## N-I dash/bash/mksh/ash STDOUT:
70 x=hi
71 ---
72 ## END
73
74 #### strict_errexit and command sub in export / readonly
75 case $SH in (dash|bash|mksh|ash) exit ;; esac
76
77 $SH -o errexit -O strict_errexit -c 'echo a; export x=$(might-fail); echo b'
78 echo status=$?
79 $SH -o errexit -O strict_errexit -c 'echo a; readonly x=$(might-fail); echo b'
80 echo status=$?
81 $SH -o errexit -O strict_errexit -c 'echo a; x=$(true); echo b'
82 echo status=$?
83
84 ## STDOUT:
85 a
86 status=1
87 a
88 status=1
89 a
90 b
91 status=0
92 ## END
93 ## N-I dash/bash/mksh/ash stdout-json: ""
94
95
96 #### strict_errexit allows pipeline because you can set -o pipefail
97 case $SH in (dash|ash) exit ;; esac
98
99 set -o errexit
100 set -o pipefail
101 shopt -s strict_errexit || true
102
103 if echo 1 | grep 1; then
104 echo one
105 fi
106
107 #{ echo 3; exit 3; } | grep 3
108 #echo status ${PIPESTATUS[@]}
109
110 if { echo 5; exit 5; } | grep 5; then
111 echo 'should not succeed'
112 else
113 echo status ${PIPESTATUS[@]}
114 fi
115 ## STDOUT:
116 1
117 one
118 5
119 status 5 0
120 ## END
121 ## N-I dash/ash stdout-json: ""
122
123 #### strict_errexit does NOT affect inside of function
124 shopt -s strict_errexit || true
125
126 p() {
127 echo before
128 local x
129 x=$(false)
130 echo x=$x
131 }
132
133 if p; then
134 echo ok
135 fi
136
137 shopt -s command_sub_errexit || true
138
139 if p; then
140 echo ok
141 fi
142
143 ## status: 1
144 ## STDOUT:
145 before
146 x=
147 ok
148 before
149 ## END
150 ## OK dash/bash/mksh/ash status: 0
151 ## OK dash/bash/mksh/ash STDOUT:
152 before
153 x=
154 ok
155 before
156 x=
157 ok
158 ## END
159
160 #### strict_errexit does NOT affect inside of proc
161 shopt -s strict_errexit
162
163 proc p() {
164 echo before
165 const x = $(false)
166 echo x=$x
167 }
168
169 if p; then
170 echo ok
171 fi
172
173 shopt -s command_sub_errexit
174
175 if p; then
176 echo ok
177 fi
178
179 ## status: 1
180 ## STDOUT:
181 before
182 x=
183 ok
184 before
185 ## END
186 ## N-I dash/bash/mksh/ash stdout-json: ""
187 ## N-I dash/bash/ash status: 2
188 ## N-I mksh status: 1
189
190 #### command sub with command_sub_errexit only
191 set -o errexit
192 shopt -s command_sub_errexit || true
193 echo zero
194 echo $(echo one; false; echo two) # bash/ash keep going
195 echo parent status=$?
196 ## STDOUT:
197 zero
198 one two
199 parent status=0
200 ## END
201 ## N-I dash/mksh STDOUT:
202 zero
203 one
204 parent status=0
205 ## END
206
207 #### command sub with inherit_errexit and command_sub_errexit
208 set -o errexit
209
210 # bash implements inherit_errexit, but it's not as strict as OSH.
211 shopt -s inherit_errexit || true
212 shopt -s command_sub_errexit || true
213 echo zero
214 echo $(echo one; false; echo two) # bash/ash keep going
215 echo parent status=$?
216 ## STDOUT:
217 zero
218 ## END
219 ## status: 1
220 ## N-I dash/mksh/bash status: 0
221 ## N-I dash/mksh/bash STDOUT:
222 zero
223 one
224 parent status=0
225 ## END
226 ## N-I ash status: 0
227 ## N-I ash STDOUT:
228 zero
229 one two
230 parent status=0
231 ## END
232
233 #### command sub: last command fails but keeps going and exit code is 0
234 set -o errexit
235 echo $(echo one; false) # we lost the exit code
236 echo status=$?
237 ## STDOUT:
238 one
239 status=0
240 ## END
241
242 #### global assignment with command sub: middle command fails
243 set -o errexit
244 s=$(echo one; false; echo two;)
245 echo "$s"
246 ## status: 0
247 ## STDOUT:
248 one
249 two
250 ## END
251 # dash and mksh: whole thing aborts!
252 ## OK dash/mksh stdout-json: ""
253 ## OK dash/mksh status: 1
254
255 #### global assignment with command sub: last command fails and it aborts
256 set -o errexit
257 s=$(echo one; false)
258 echo status=$?
259 ## stdout-json: ""
260 ## status: 1
261
262 #### local: middle command fails and keeps going
263 set -o errexit
264 f() {
265 echo good
266 local x=$(echo one; false; echo two)
267 echo status=$?
268 echo $x
269 }
270 f
271 ## STDOUT:
272 good
273 status=0
274 one two
275 ## END
276 # for dash and mksh, the INNER shell aborts, but the outer one keeps going!
277 ## OK dash/mksh STDOUT:
278 good
279 status=0
280 one
281 ## END
282
283 #### local: last command fails and also keeps going
284 set -o errexit
285 f() {
286 echo good
287 local x=$(echo one; false)
288 echo status=$?
289 echo $x
290 }
291 f
292 ## STDOUT:
293 good
294 status=0
295 one
296 ## END
297
298 #### local and inherit_errexit / command_sub_errexit
299 # I've run into this problem a lot.
300 set -o errexit
301 shopt -s inherit_errexit || true # bash option
302 shopt -s command_sub_errexit || true # oil option
303 f() {
304 echo good
305 local x=$(echo one; false; echo two)
306 echo status=$?
307 echo $x
308 }
309 f
310 ## status: 1
311 ## STDOUT:
312 good
313 ## END
314 ## N-I ash status: 0
315 ## N-I ash STDOUT:
316 good
317 status=0
318 one two
319 ## END
320 ## N-I bash/dash/mksh status: 0
321 ## N-I bash/dash/mksh STDOUT:
322 good
323 status=0
324 one
325 ## END
326
327 #### global assignment when last status is failure
328 # this is a bug I introduced
329 set -o errexit
330 x=$(false) || true # from abuild
331 [ -n "$APORTSDIR" ] && true
332 BUILDDIR=${_BUILDDIR-$BUILDDIR}
333 echo status=$?
334 ## STDOUT:
335 status=0
336 ## END
337
338 #### strict_errexit prevents errexit from being disabled in function
339 set -o errexit
340 fun() { echo fun; }
341
342 fun || true # this is OK
343
344 shopt -s strict_errexit || true
345
346 echo 'builtin ok' || true
347 env echo 'external ok' || true
348
349 fun || true # this fails
350
351 ## status: 1
352 ## STDOUT:
353 fun
354 builtin ok
355 external ok
356 ## END
357 ## N-I dash/bash/mksh/ash status: 0
358 ## N-I dash/bash/mksh/ash STDOUT:
359 fun
360 builtin ok
361 external ok
362 fun
363 ## END
364
365 #### strict_errexit prevents errexit from being disabled in brace group
366 set -o errexit
367 # false failure is NOT respected either way
368 { echo foo; false; echo bar; } || echo "failed"
369
370 shopt -s strict_errexit || true
371 { echo foo; false; echo bar; } || echo "failed"
372 ## status: 1
373 ## STDOUT:
374 foo
375 bar
376 ## END
377
378 ## N-I dash/bash/mksh/ash status: 0
379 ## N-I dash/bash/mksh/ash STDOUT:
380 foo
381 bar
382 foo
383 bar
384 ## END
385
386 #### strict_errexit prevents errexit from being disabled in subshell
387 set -o errexit
388 shopt -s inherit_errexit || true
389
390 # false failure is NOT respected either way
391 ( echo foo; false; echo bar; ) || echo "failed"
392
393 shopt -s strict_errexit || true
394 ( echo foo; false; echo bar; ) || echo "failed"
395 ## status: 1
396 ## STDOUT:
397 foo
398 bar
399 ## END
400
401 ## N-I dash/bash/mksh/ash status: 0
402 ## N-I dash/bash/mksh/ash STDOUT:
403 foo
404 bar
405 foo
406 bar
407 ## END
408
409 #### strict_errexit and ! && || if while until
410 prelude='set -o errexit
411 shopt -s strict_errexit || true
412 fun() { echo fun; }'
413
414 $SH -c "$prelude; ! fun; echo 'should not get here'"
415 echo bang=$?
416 echo --
417
418 $SH -c "$prelude; fun || true"
419 echo or=$?
420 echo --
421
422 $SH -c "$prelude; fun && true"
423 echo and=$?
424 echo --
425
426 $SH -c "$prelude; if fun; then true; fi"
427 echo if=$?
428 echo --
429
430 $SH -c "$prelude; while fun; do echo while; exit; done"
431 echo while=$?
432 echo --
433
434 $SH -c "$prelude; until fun; do echo until; exit; done"
435 echo until=$?
436 echo --
437
438
439 ## STDOUT:
440 bang=1
441 --
442 or=1
443 --
444 and=1
445 --
446 if=1
447 --
448 while=1
449 --
450 until=1
451 --
452 ## END
453 ## N-I dash/bash/mksh/ash STDOUT:
454 fun
455 should not get here
456 bang=0
457 --
458 fun
459 or=0
460 --
461 fun
462 and=0
463 --
464 fun
465 if=0
466 --
467 fun
468 while
469 while=0
470 --
471 fun
472 until=0
473 --
474 ## END
475
476 #### if pipeline doesn't fail fatally
477 set -o errexit
478 set -o pipefail
479
480 f() {
481 local dir=$1
482 if ls $dir | grep ''; then
483 echo foo
484 echo ${PIPESTATUS[@]}
485 fi
486 }
487 rmdir $TMP/_tmp || true
488 rm -f $TMP/*
489 f $TMP
490 f /nonexistent # should fail
491 echo done
492
493 ## N-I dash status: 2
494 ## N-I dash stdout-json: ""
495 ## STDOUT:
496 done
497 ## END
498
499 #### errexit is silent (verbose_errexit for Oil)
500 shopt -u verbose_errexit 2>/dev/null || true
501 set -e
502 false
503 ## stderr-json: ""
504 ## status: 1
505
506 #### command sub errexit preserves exit code
507 set -e
508 shopt -s command_sub_errexit || true
509
510 echo before
511 echo $(exit 42)
512 echo after
513 ## STDOUT:
514 before
515 ## END
516 ## status: 42
517 ## N-I dash/bash/mksh/ash STDOUT:
518 before
519
520 after
521 ## N-I dash/bash/mksh/ash status: 0
522
523 #### strict_errexit without errexit
524 myproc() {
525 echo myproc
526 }
527 myproc || true
528
529 # This should be a no-op I guess
530 shopt -s strict_errexit || true
531 myproc || true
532
533 ## STDOUT:
534 myproc
535 myproc
536 ## END
537
538
539 #### What's in strict:all?
540
541 # inherit_errexit, strict_errexit, but not command_sub_errexit!
542 # for that you need oil:basic!
543
544 set -o errexit
545 shopt -s strict:all || true
546
547 # inherit_errexit is bash compatible, so we have it
548 #echo $(date %x)
549
550 # command_sub_errexit would hide errors!
551 f() {
552 local d=$(date %x)
553 }
554 f
555
556 deploy_func() {
557 echo one
558 false
559 echo two
560 }
561
562 if ! deploy_func; then
563 echo failed
564 fi
565
566 echo 'should not get here'
567
568 ## status: 1
569 ## STDOUT:
570 ## END
571 ## N-I dash/bash/mksh/ash status: 0
572 ## N-I dash/bash/mksh/ash STDOUT:
573 one
574 two
575 should not get here
576 ## END
577
578 #### command_sub_errexit causes local d=$(date %x) to fail
579 set -o errexit
580 shopt -s inherit_errexit || true
581 #shopt -s strict_errexit || true
582 shopt -s command_sub_errexit || true
583
584 myproc() {
585 # this is disallowed because we want a runtime error 100% of the time
586 local x=$(true)
587
588 # Realistic example. Should fail here but shells don't!
589 local d=$(date %x)
590 echo hi
591 }
592 myproc
593
594 ## status: 1
595 ## STDOUT:
596 ## END
597 ## N-I dash/bash/mksh/ash status: 0
598 ## N-I dash/bash/mksh/ash STDOUT:
599 hi
600 ## END
601
602 #### command_sub_errexit and command sub in array
603 case $SH in (dash|ash|mksh) exit ;; esac
604
605 set -o errexit
606 shopt -s inherit_errexit || true
607 #shopt -s strict_errexit || true
608 shopt -s command_sub_errexit || true
609
610 # We don't want silent failure here
611 readonly -a myarray=( one "$(date %x)" two )
612
613 #echo len=${#myarray[@]}
614 argv.py "${myarray[@]}"
615 ## status: 1
616 ## STDOUT:
617 ## END
618 ## N-I bash status: 0
619 ## N-I bash STDOUT:
620 ['one', '', 'two']
621 ## END
622 ## N-I dash/ash/mksh status: 0
623
624 #### OLD: command sub in conditional, with inherit_errexit
625 set -o errexit
626 shopt -s inherit_errexit || true
627 if echo $(echo 1; false; echo 2); then
628 echo A
629 fi
630 echo done
631
632 ## STDOUT:
633 1 2
634 A
635 done
636 ## END
637 ## N-I dash/mksh STDOUT:
638 1
639 A
640 done
641 ## END
642
643 #### OLD: command sub in redirect in conditional
644 set -o errexit
645
646 if echo tmp_contents > $(echo tmp); then
647 echo 2
648 fi
649 cat tmp
650 ## STDOUT:
651 2
652 tmp_contents
653 ## END
654
655 #### Regression
656 case $SH in (bash|dash|ash|mksh) exit ;; esac
657
658 shopt --set oil:basic
659
660 shopt --unset errexit {
661 echo hi
662 }
663
664 proc p {
665 echo p
666 }
667
668 shopt --unset errexit {
669 p
670 }
671 ## STDOUT:
672 hi
673 p
674 ## END
675 ## N-I bash/dash/ash/mksh stdout-json: ""