1 ## oils_failures_allowed: 2
2 ## compare_shells: bash
3
4 # Notes on bash semantics:
5
6 # https://www.gnu.org/software/bash/manual/bash.html
7 #
8 # Commands specified with a DEBUG trap are executed before every simple
9 # command, for command, case command, select command, every arithmetic for
10 # command, and before the first command executes in a shell function. The DEBUG
11 # trap is not inherited by shell functions unless the function has been given
12 # the trace attribute or the functrace option has been enabled using the shopt
13 # builtin. The extdebug shell option has additional effects on the DEBUG trap.
14
15 #### trap -l
16 trap -l | grep INT >/dev/null
17 ## status: 0
18
19
20 #### trap -p
21
22 trap 'echo exit' EXIT
23
24 trap -p > parent.txt
25
26 grep EXIT parent.txt >/dev/null
27 if test $? -eq 0; then
28 echo shown
29 else
30 echo not shown
31 fi
32
33 ## STDOUT:
34 shown
35 exit
36 ## END
37
38 #### trap -p in child is BUGGY in bash
39
40 # It shows the trap even though it doesn't execute it!
41
42 trap 'echo exit' EXIT
43
44 trap -p | cat > child.txt
45
46 grep EXIT child.txt >/dev/null
47 if test $? -eq 0; then
48 echo shown
49 else
50 echo not shown
51 fi
52
53 ## STDOUT:
54 not shown
55 exit
56 ## END
57 ## BUG bash STDOUT:
58 shown
59 exit
60 ## END
61
62 #### trap DEBUG ignores $?
63 debuglog() {
64 echo " [$@]"
65 return 42 # IGNORED FAILURE
66 }
67
68 trap 'debuglog $LINENO' DEBUG
69
70 echo status=$?
71 echo A
72 echo status=$?
73 echo B
74 echo status=$?
75
76 ## STDOUT:
77 [8]
78 status=0
79 [9]
80 A
81 [10]
82 status=0
83 [11]
84 B
85 [12]
86 status=0
87 ## END
88
89 #### but trap DEBUG respects errexit
90 set -o errexit
91
92 debuglog() {
93 echo " [$@]"
94 return 42
95 }
96
97 trap 'debuglog $LINENO' DEBUG
98
99 echo status=$?
100 echo A
101 echo status=$?
102 echo B
103 echo status=$?
104
105 ## status: 42
106 ## STDOUT:
107 [10]
108 ## END
109
110 #### trap DEBUG with 'return'
111
112 debuglog() {
113 echo " [$@]"
114 }
115
116 trap 'debuglog $LINENO; return 42' DEBUG
117
118 echo status=$?
119 echo A
120 echo status=$?
121 echo B
122 echo status=$?
123
124 ## status: 0
125
126 ## STDOUT:
127 [7]
128 status=0
129 [8]
130 A
131 [9]
132 status=0
133 [10]
134 B
135 [11]
136 status=0
137 ## END
138
139 # OSH doesn't ignore this
140
141 ## OK osh status: 42
142 ## OK osh STDOUT:
143 [7]
144 ## END
145
146 #### trap DEBUG with 'exit'
147 debuglog() {
148 echo " [$@]"
149 }
150
151 trap 'debuglog $LINENO; exit 42' DEBUG
152
153 echo status=$?
154 echo A
155 echo status=$?
156 echo B
157 echo status=$?
158
159 ## status: 42
160 ## STDOUT:
161 [7]
162 ## END
163
164
165
166 #### trap DEBUG with non-compound commands
167 case $SH in (dash|mksh) exit ;; esac
168
169 debuglog() {
170 echo " [$@]"
171 }
172 trap 'debuglog $LINENO' DEBUG
173
174 echo a
175 echo b; echo c
176
177 echo d && echo e
178 echo f || echo g
179
180 (( h = 42 ))
181 [[ j == j ]]
182
183 var=value
184
185 readonly r=value
186
187 ## STDOUT:
188 [8]
189 a
190 [9]
191 b
192 [9]
193 c
194 [11]
195 d
196 [11]
197 e
198 [12]
199 f
200 [14]
201 [15]
202 [17]
203 [19]
204 ## END
205
206 #### trap DEBUG and control flow
207
208 debuglog() {
209 echo " [$@]"
210 }
211 trap 'debuglog $LINENO' DEBUG
212
213 while true; do
214 echo hello
215 break
216 done
217
218 ## STDOUT:
219 [6]
220 [7]
221 hello
222 [8]
223 ## END
224 ## STDOUT:
225 [6]
226 [7]
227 hello
228 [8]
229 ## END
230
231 #### trap DEBUG and command sub / subshell
232 case $SH in (dash|mksh) exit ;; esac
233
234 debuglog() {
235 echo " [$@]"
236 }
237 trap 'debuglog $LINENO' DEBUG
238
239 echo "result = $(echo command sub; echo two)"
240 ( echo subshell
241 echo two
242 )
243 echo done
244
245 ## STDOUT:
246 [8]
247 result = command sub
248 two
249 subshell
250 two
251 [12]
252 done
253 ## END
254
255 #### trap DEBUG not run in forked interpreter for first pipeline part
256
257 debuglog() {
258 #echo " PID=$$ BASHPID=$BASHPID LINENO=$1"
259 echo " LINENO=$1"
260 }
261 trap 'debuglog $LINENO' DEBUG
262
263 { echo pipe1;
264 echo pipe2; } \
265 | cat
266 echo ok
267
268 ## STDOUT:
269 LINENO=8
270 pipe1
271 pipe2
272 LINENO=9
273 ok
274 ## END
275
276 #### One 'echo' in first pipeline part - why does bash behave differently from case above?
277
278 # TODO: bash runs the trap 3 times, and osh only twice. I don't see why. Is
279 # it because Process::Run() does trap_state.ClearForSubProgram()? Probably
280 #echo top PID=$$ BASHPID=$BASHPID
281 #shopt -s lastpipe
282
283 debuglog() {
284 #echo " PID=$$ BASHPID=$BASHPID LINENO=$1"
285 #echo " LINENO=$1 $BASH_COMMAND"
286 # LINENO=6 echo pipeline
287 # LINENO=7 cat
288 echo " LINENO=$1"
289 }
290 trap 'debuglog $LINENO' DEBUG
291
292 echo pipeline \
293 | cat
294 echo ok
295
296 ## STDOUT:
297 LINENO=6
298 LINENO=7
299 pipeline
300 LINENO=8
301 ok
302 ## END
303 ## OK osh STDOUT:
304 LINENO=7
305 pipeline
306 LINENO=8
307 ok
308 ## END
309
310 #### trap DEBUG and pipeline (lastpipe difference)
311 debuglog() {
312 echo " [$@]"
313 }
314 trap 'debuglog $LINENO' DEBUG
315
316 # gets run for each one of these
317 { echo a; echo b; }
318
319 # only run for the last one, maybe I guess because traps aren't inherited?
320 { echo x; echo y; } | wc -l
321
322 # bash runs for all of these, but OSH doesn't because we have SubProgramThunk
323 # Hm.
324 date | cat | wc -l
325
326 date |
327 cat |
328 wc -l
329
330 ## STDOUT:
331 [6]
332 a
333 [6]
334 b
335 [8]
336 2
337 [10]
338 [10]
339 [10]
340 1
341 [12]
342 [13]
343 [14]
344 1
345 ## END
346
347 # Marking OK due to lastpipe execution difference
348
349 ## OK osh STDOUT:
350 [6]
351 a
352 [6]
353 b
354 [8]
355 2
356 [10]
357 1
358 [14]
359 1
360 ## END
361
362 #### trap DEBUG function call
363 debuglog() {
364 echo " [$@]"
365 }
366 trap 'debuglog $LINENO' DEBUG
367
368 f() {
369 local mylocal=1
370 for i in "$@"; do
371 echo i=$i
372 done
373 }
374
375 f A B # executes ONCE here, but does NOT go into the function call
376
377 echo next
378
379 f X Y
380
381 echo ok
382
383 ## STDOUT:
384 [13]
385 i=A
386 i=B
387 [15]
388 next
389 [17]
390 i=X
391 i=Y
392 [19]
393 ok
394 ## END
395
396 #### trap DEBUG case
397 debuglog() {
398 echo " [$@]"
399 }
400 trap 'debuglog $LINENO' DEBUG
401
402 name=foo.py
403
404 case $name in
405 *.py)
406 echo python
407 ;;
408 *.sh)
409 echo shell
410 ;;
411 esac
412 echo ok
413
414 ## STDOUT:
415 [6]
416 [8]
417 [10]
418 python
419 [16]
420 ok
421 ## END
422
423 #### trap DEBUG for each
424
425 debuglog() {
426 echo " [$@]"
427 }
428 trap 'debuglog $LINENO' DEBUG
429
430 for x in 1 2; do
431 echo x=$x
432 done
433
434 echo ok
435
436 ## STDOUT:
437 [6]
438 [7]
439 x=1
440 [6]
441 [7]
442 x=2
443 [10]
444 ok
445 ## END
446
447 # NOT matching bash right now because 'while' loops don't have it
448 # And we have MORE LOOPS
449 #
450 # What we really need is a trap that runs in the main loop and TELLS you what
451 # kind of node it is?
452
453 ## N-I osh STDOUT:
454 [7]
455 x=1
456 [7]
457 x=2
458 [10]
459 ok
460 ## END
461
462 #### trap DEBUG for expr
463 debuglog() {
464 echo " [$@]"
465 }
466 trap 'debuglog $LINENO' DEBUG
467
468 for (( i =3 ; i < 5; ++i )); do
469 echo i=$i
470 done
471
472 echo ok
473
474 ## STDOUT:
475 [6]
476 [6]
477 [7]
478 i=3
479 [6]
480 [6]
481 [7]
482 i=4
483 [6]
484 [6]
485 [10]
486 ok
487 ## END
488 ## N-I osh STDOUT:
489 [7]
490 i=3
491 [7]
492 i=4
493 [10]
494 ok
495 ## END
496
497 #### trap DEBUG if while
498 debuglog() {
499 echo " [$@]"
500 }
501 trap 'debuglog $LINENO' DEBUG
502
503 if test x = x; then
504 echo if
505 fi
506
507 while test x != x; do
508 echo while
509 done
510
511 ## STDOUT:
512 [6]
513 [7]
514 if
515 [10]
516 ## END
517
518
519 #### trap RETURN
520 profile() {
521 echo "profile [$@]"
522 }
523 g() {
524 echo --
525 echo g
526 echo --
527 return
528 }
529 f() {
530 echo --
531 echo f
532 echo --
533 g
534 }
535 # RETURN trap doesn't fire when a function returns, only when a script returns?
536 # That's not what the manual says.
537 trap 'profile x y' RETURN
538 f
539 . $REPO_ROOT/spec/testdata/return-helper.sh
540 ## status: 42
541 ## STDOUT:
542 --
543 f
544 --
545 --
546 g
547 --
548 return-helper.sh
549 profile [x y]
550 ## END
551
552 #### Compare trap DEBUG vs. trap ERR
553
554 # Pipelines and AndOr are problematic
555
556 # THREE each
557 trap 'echo dbg $LINENO' DEBUG
558
559 false | false | false
560
561 false || false || false
562
563 ! true
564
565 trap - DEBUG
566
567
568 # ONE EACH
569 trap 'echo err $LINENO' ERR
570
571 false | false | false
572
573 false || false || false
574
575 ! true # not run
576
577 echo ok
578
579 ## STDOUT:
580 dbg 3
581 dbg 3
582 dbg 3
583 dbg 5
584 dbg 5
585 dbg 5
586 dbg 7
587 dbg 9
588 err 14
589 err 16
590 ok
591 ## END
592
593
594 #### Combine DEBUG trap and USR1 trap
595
596 case $SH in dash|mksh|ash) exit ;; esac
597
598 trap 'false; echo $LINENO usr1' USR1
599 trap 'false; echo $LINENO dbg' DEBUG
600
601 sh -c "kill -USR1 $$"
602 echo after=$?
603
604 ## STDOUT:
605 6 dbg
606 1 dbg
607 1 dbg
608 1 usr1
609 7 dbg
610 after=0
611 ## END
612
613 ## N-I dash/mksh/ash STDOUT:
614 ## END
615
616 #### Combine ERR trap and USR1 trap
617
618 case $SH in dash|mksh|ash) exit ;; esac
619
620 trap 'false; echo $LINENO usr1' USR1
621 trap 'false; echo $LINENO err' ERR
622
623 sh -c "kill -USR1 $$"
624 echo after=$?
625
626 ## STDOUT:
627 1 err
628 1 usr1
629 after=0
630 ## END
631
632 ## N-I dash/mksh/ash STDOUT:
633 ## END
634
635 #### Combine DEBUG trap and ERR trap
636
637 case $SH in dash|mksh|ash) exit ;; esac
638
639 trap 'false; echo $LINENO err' ERR
640 trap 'false; echo $LINENO debug' DEBUG
641
642 false
643 echo after=$?
644
645 ## STDOUT:
646 6 err
647 6 debug
648 6 debug
649 6 debug
650 6 err
651 7 err
652 7 debug
653 after=1
654 ## END
655
656 ## N-I dash/mksh/ash STDOUT:
657 ## END
658
659 #### trap ERR with errtrace (-E)
660 # ERR trap is not inherited by shell functions, sub processes, sub commands, ... unless the -o errtrace option is set
661 set -E
662 trap "echo ERR" ERR
663 echo 0
664 false
665 echo 1
666 cat <( date X ; echo 2 )
667 echo $( date X ; echo 3 )
668 { date X ; echo 4 ; }
669 { date X ; echo 5 ; } & wait
670 ( date X ; echo 6 )
671 f() { date X ; echo 7 ; } ; f
672 x=$( date X ; echo 8 )
673 true
674 ## status: 0
675 ## STDOUT:
676 0
677 ERR
678 1
679 ERR
680 2
681 ERR 3
682 ERR
683 4
684 ERR
685 5
686 ERR
687 6
688 ERR
689 7
690 ## END
691
692 #### trap ERR without errtrace (+E)
693 # ERR trap is not inherited by shell functions, sub processes, sub commands, ... unless the -o errtrace option is set
694 set +E
695 trap "echo ERR" ERR
696 echo 0
697 false
698 echo 1
699 cat <( date X ; echo 2 )
700 echo $( date X ; echo 3 )
701 { date X ; echo 4 ; }
702 { date X ; echo 5 ; } & wait
703 ( date X ; echo 6 )
704 f() { date X ; echo 7 ; } ; f
705 x=$( date X ; echo 8 )
706 true
707 ## status: 0
708 ## STDOUT:
709 0
710 ERR
711 1
712 2
713 3
714 ERR
715 4
716 5
717 6
718 7
719 ## END
720
721 #### trap ERR LINENO
722 # ERR trap is not inherited by shell functions, sub processes, sub commands, ... unless the -o errtrace option is set
723 set +E
724 trap 'echo ERR=$LINENO' ERR
725 echo 0
726 false
727 true
728 ## status: 0
729 ## STDOUT:
730 0
731 ERR=4
732 ## END
733
734 #### trap ERR NoLastFork
735 trap 'echo ERR' ERR ; date X
736 ## status: 1
737 ## STDOUT:
738 ERR
739 ## END
740
741 #### trap ERR shadowing with errtrace (-E)
742 # ERR trap is not inherited by shell functions, sub processes, sub commands, ... unless the -o errtrace option is set
743 set -E
744 trap "echo ERR" ERR
745 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
746 cat <( trap "echo ERR2" ERR ; date X )
747 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
748 echo $( trap "echo ERR3" ERR ; date X )
749 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
750 { trap "echo ERR4" ERR ; date X ; }
751 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
752 { trap "echo ERR5" ERR ; date X ; } & wait
753 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
754 ( trap "echo ERR6" ERR ; date X )
755 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
756 f() { trap "echo ERR7" ERR ; date X ; } ; f
757 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
758 x=$( trap "echo ERR8" ERR ; date X ; echo 8 )
759 true
760 ## status: 0
761 ## STDOUT:
762 ERR
763 ERRx
764 ERR2
765 ERR
766 ERRx
767 ERR3
768 ERR
769 ERRx
770 ERR4
771 ERR4
772 ERRx
773 ERR5
774 ERR
775 ERRx
776 ERR6
777 ERR
778 ERR
779 ERRx
780 ERR7
781 ERR7
782 ERR7
783 ERRx
784 ## END
785
786 #### trap ERR shadowing without errtrace (+E)
787 # ERR trap is not inherited by shell functions, sub processes, sub commands, ... unless the -o errtrace option is set
788 set +E
789 trap "echo ERR" ERR
790 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
791 cat <( trap "echo ERR2" ERR ; date X )
792 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
793 echo $( trap "echo ERR3" ERR ; date X )
794 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
795 { trap "echo ERR4" ERR ; date X ; }
796 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
797 { trap "echo ERR5" ERR ; date X ; } & wait
798 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
799 ( trap "echo ERR6" ERR ; date X )
800 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
801 f() { trap "echo ERR7" ERR ; date X ; } ; f
802 false ; trap "echo ERRx" ERR ; false ; trap "echo ERR" ERR
803 x=$( trap "echo ERR8" ERR ; date X ; echo 8 )
804 true
805 ## status: 0
806 ## STDOUT:
807 ERR
808 ERRx
809 ERR2
810 ERR
811 ERRx
812 ERR3
813 ERR
814 ERRx
815 ERR4
816 ERR4
817 ERRx
818 ERR5
819 ERR
820 ERRx
821 ERR6
822 ERR
823 ERR
824 ERRx
825 ERR7
826 ERR7
827 ERR7
828 ERRx
829 ## END