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 |
# In other words, BASH_SOURCE is about the DEFINITION. While FUNCNAME and |
25 |
# BASH_LINENO are about the CALL. |
26 |
|
27 |
|
28 |
#### ${FUNCNAME[@]} array |
29 |
g() { |
30 |
argv.py "${FUNCNAME[@]}" |
31 |
} |
32 |
f() { |
33 |
argv.py "${FUNCNAME[@]}" |
34 |
g |
35 |
argv.py "${FUNCNAME[@]}" |
36 |
} |
37 |
f |
38 |
## STDOUT: |
39 |
['f'] |
40 |
['g', 'f'] |
41 |
['f'] |
42 |
## END |
43 |
|
44 |
#### FUNCNAME with source (scalar or array) |
45 |
cd $REPO_ROOT |
46 |
|
47 |
# Comments on bash quirk: |
48 |
# https://github.com/oilshell/oil/pull/656#issuecomment-599162211 |
49 |
|
50 |
f() { |
51 |
. spec/testdata/echo-funcname.sh |
52 |
} |
53 |
g() { |
54 |
f |
55 |
} |
56 |
|
57 |
g |
58 |
echo ----- |
59 |
|
60 |
. spec/testdata/echo-funcname.sh |
61 |
echo ----- |
62 |
|
63 |
argv.py "${FUNCNAME[@]}" |
64 |
|
65 |
# Show bash inconsistency. FUNCNAME doesn't behave like a normal array. |
66 |
case $SH in |
67 |
(bash) |
68 |
echo ----- |
69 |
a=('A') |
70 |
argv.py ' @' "${a[@]}" |
71 |
argv.py ' 0' "${a[0]}" |
72 |
argv.py '${}' "${a}" |
73 |
argv.py ' $' "$a" |
74 |
;; |
75 |
esac |
76 |
|
77 |
## STDOUT: |
78 |
[' @', 'source', 'f', 'g'] |
79 |
[' 0', 'source'] |
80 |
['${}', 'source'] |
81 |
[' $', 'source'] |
82 |
----- |
83 |
[' @', 'source'] |
84 |
[' 0', 'source'] |
85 |
['${}', 'source'] |
86 |
[' $', 'source'] |
87 |
----- |
88 |
[] |
89 |
## END |
90 |
## BUG bash STDOUT: |
91 |
[' @', 'source', 'f', 'g'] |
92 |
[' 0', 'source'] |
93 |
['${}', 'source'] |
94 |
[' $', 'source'] |
95 |
----- |
96 |
[' @', 'source'] |
97 |
[' 0', 'source'] |
98 |
['${}', ''] |
99 |
[' $', ''] |
100 |
----- |
101 |
[] |
102 |
----- |
103 |
[' @', 'A'] |
104 |
[' 0', 'A'] |
105 |
['${}', 'A'] |
106 |
[' $', 'A'] |
107 |
## END |
108 |
|
109 |
|
110 |
#### BASH_SOURCE and BASH_LINENO scalar or array (e.g. for virtualenv) |
111 |
cd $REPO_ROOT |
112 |
|
113 |
# https://github.com/pypa/virtualenv/blob/master/virtualenv_embedded/activate.sh |
114 |
# https://github.com/akinomyoga/ble.sh/blob/6f6c2e5/ble.pp#L374 |
115 |
|
116 |
argv.py "$BASH_SOURCE" # SimpleVarSub |
117 |
argv.py "${BASH_SOURCE}" # BracedVarSub |
118 |
argv.py "$BASH_LINENO" # SimpleVarSub |
119 |
argv.py "${BASH_LINENO}" # BracedVarSub |
120 |
argv.py "$FUNCNAME" # SimpleVarSub |
121 |
argv.py "${FUNCNAME}" # BracedVarSub |
122 |
echo __ |
123 |
source spec/testdata/bash-source-string.sh |
124 |
|
125 |
## STDOUT: |
126 |
[''] |
127 |
[''] |
128 |
[''] |
129 |
[''] |
130 |
[''] |
131 |
[''] |
132 |
__ |
133 |
['spec/testdata/bash-source-string.sh'] |
134 |
['spec/testdata/bash-source-string.sh'] |
135 |
['9'] |
136 |
['9'] |
137 |
____ |
138 |
['spec/testdata/bash-source-string2.sh'] |
139 |
['spec/testdata/bash-source-string2.sh'] |
140 |
['11'] |
141 |
['11'] |
142 |
## END |
143 |
|
144 |
|
145 |
#### ${FUNCNAME} with prefix/suffix operators |
146 |
shopt -s compat_array |
147 |
|
148 |
check() { |
149 |
argv.py "${#FUNCNAME}" |
150 |
argv.py "${FUNCNAME::1}" |
151 |
argv.py "${FUNCNAME:1}" |
152 |
} |
153 |
check |
154 |
## STDOUT: |
155 |
['5'] |
156 |
['c'] |
157 |
['heck'] |
158 |
## END |
159 |
|
160 |
#### operators on FUNCNAME not allowed by default |
161 |
check() { |
162 |
argv.py "${FUNCNAME}" |
163 |
argv.py "${#FUNCNAME}" |
164 |
argv.py "${FUNCNAME::1}" |
165 |
argv.py "${FUNCNAME:1}" |
166 |
} |
167 |
check |
168 |
## status: 1 |
169 |
## STDOUT: |
170 |
['check'] |
171 |
## END |
172 |
## OK bash status: 0 |
173 |
## OK bash STDOUT: |
174 |
['check'] |
175 |
['5'] |
176 |
['c'] |
177 |
['heck'] |
178 |
## END |
179 |
|
180 |
#### ${FUNCNAME} and "set -u" (OSH regression) |
181 |
set -u |
182 |
argv.py "$FUNCNAME" |
183 |
## status: 1 |
184 |
## stdout-json: "" |
185 |
|
186 |
#### $((BASH_LINENO)) (scalar form in arith) |
187 |
check() { |
188 |
echo $((BASH_LINENO)) |
189 |
} |
190 |
check |
191 |
## stdout: 4 |
192 |
|
193 |
#### ${BASH_SOURCE[@]} with source and function name |
194 |
cd $REPO_ROOT |
195 |
|
196 |
argv.py "${BASH_SOURCE[@]}" |
197 |
source spec/testdata/bash-source-simple.sh |
198 |
f |
199 |
## STDOUT: |
200 |
[] |
201 |
['spec/testdata/bash-source-simple.sh'] |
202 |
['spec/testdata/bash-source-simple.sh'] |
203 |
## END |
204 |
|
205 |
#### ${BASH_SOURCE[@]} with line numbers |
206 |
cd $REPO_ROOT |
207 |
|
208 |
$SH spec/testdata/bash-source.sh |
209 |
## STDOUT: |
210 |
['begin F funcs', 'f', 'main'] |
211 |
['begin F files', 'spec/testdata/bash-source.sh', 'spec/testdata/bash-source.sh'] |
212 |
['begin F lines', '21', '0'] |
213 |
['G funcs', 'g', 'f', 'main'] |
214 |
['G files', 'spec/testdata/bash-source-2.sh', 'spec/testdata/bash-source.sh', 'spec/testdata/bash-source.sh'] |
215 |
['G lines', '15', '21', '0'] |
216 |
['end F funcs', 'f', 'main'] |
217 |
['end F', 'spec/testdata/bash-source.sh', 'spec/testdata/bash-source.sh'] |
218 |
['end F lines', '21', '0'] |
219 |
## END |
220 |
|
221 |
#### ${BASH_LINENO[@]} is a stack of line numbers for function calls |
222 |
# note: it's CALLS, not DEFINITIONS. |
223 |
g() { |
224 |
argv.py G "${BASH_LINENO[@]}" |
225 |
} |
226 |
f() { |
227 |
argv.py 'begin F' "${BASH_LINENO[@]}" |
228 |
g # line 6 |
229 |
argv.py 'end F' "${BASH_LINENO[@]}" |
230 |
} |
231 |
argv.py ${BASH_LINENO[@]} |
232 |
f # line 9 |
233 |
## STDOUT: |
234 |
[] |
235 |
['begin F', '10'] |
236 |
['G', '6', '10'] |
237 |
['end F', '10'] |
238 |
## END |