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