1 #!/usr/bin/env bash
2 #
3 # Test call stack introspection. There are a bunch of special variables
4 # defined here:
5 #
6 # https://www.gnu.org/software/bash/manual/html_node/Bash-Variables.html
7 #
8 # - The shell function ${FUNCNAME[$i]} is defined in the file
9 # ${BASH_SOURCE[$i]} and called from ${BASH_SOURCE[$i+1]}
10 #
11 # - ${BASH_LINENO[$i]} is the line number in the source file
12 # (${BASH_SOURCE[$i+1]}) where ${FUNCNAME[$i]} was called (or
13 # ${BASH_LINENO[$i-1]} if referenced within another shell function).
14 #
15 # - For instance, ${FUNCNAME[$i]} was called from the file
16 # ${BASH_SOURCE[$i+1]} at line number ${BASH_LINENO[$i]}. The caller builtin
17 # displays the current call stack using this information.
18 #
19 # So ${BASH_SOURCE[@]} doesn't line up with ${BASH_LINENO}. But
20 # ${BASH_SOURCE[0]} does line up with $LINENO!
21 #
22 # Geez.
23
24
25 #### ${FUNCNAME[@]} array
26 g() {
27 argv.py "${FUNCNAME[@]}"
28 }
29 f() {
30 argv.py "${FUNCNAME[@]}"
31 g
32 argv.py "${FUNCNAME[@]}"
33 }
34 f
35 ## STDOUT:
36 ['f']
37 ['g', 'f']
38 ['f']
39 ## END
40
41 #### FUNCNAME with source
42 f() {
43 . spec/testdata/echo-funcname.sh
44 }
45 g() {
46 f
47 }
48 g
49 . spec/testdata/echo-funcname.sh
50 argv.py "${FUNCNAME[@]}"
51 ## STDOUT:
52 ['source', 'f', 'g']
53 ['source']
54 []
55 ## END
56
57 #### ${BASH_SOURCE[@]} is a stack of source files for function calls
58 $SH spec/testdata/bash-source.sh
59 ## STDOUT:
60 ['begin F funcs', 'f', 'main']
61 ['begin F files', 'spec/testdata/bash-source.sh', 'spec/testdata/bash-source.sh']
62 ['begin F lines', '21', '0']
63 ['G funcs', 'g', 'f', 'main']
64 ['G files', 'spec/testdata/bash-source-2.sh', 'spec/testdata/bash-source.sh', 'spec/testdata/bash-source.sh']
65 ['G lines', '15', '21', '0']
66 ['end F funcs', 'f', 'main']
67 ['end F', 'spec/testdata/bash-source.sh', 'spec/testdata/bash-source.sh']
68 ['end F lines', '21', '0']
69 ## END
70
71 #### ${BASH_LINENO[@]} is a stack of line numbers for function calls
72 # note: it's CALLS, not DEFINITIONS.
73 g() {
74 argv.py G "${BASH_LINENO[@]}"
75 }
76 f() {
77 argv.py 'begin F' "${BASH_LINENO[@]}"
78 g # line 6
79 argv.py 'end F' "${BASH_LINENO[@]}"
80 }
81 f # line 9
82 ## STDOUT:
83 ['begin F', '9']
84 ['G', '6', '9']
85 ['end F', '9']
86 ## END
87
88 #### $LINENO is the current line, not line of function call
89 g() {
90 argv.py $LINENO # line 2
91 }
92 f() {
93 argv.py $LINENO # line 5
94 g
95 argv.py $LINENO # line 7
96 }
97 f
98 ## STDOUT:
99 ['5']
100 ['2']
101 ['7']
102 ## END
103