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