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