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: "" |