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