1 #!/usr/bin/env bash
2 #
3 # Tests for builtins having to do with variables: export, readonly, unset, etc.
4 #
5 # Also see assign.test.sh.
6
7 #### Export sets a global variable
8 # Even after you do export -n, it still exists.
9 f() { export GLOBAL=X; }
10 f
11 echo $GLOBAL
12 printenv.py GLOBAL
13 ## STDOUT:
14 X
15 X
16 ## END
17
18 #### Export sets a global variable that persists after export -n
19 f() { export GLOBAL=X; }
20 f
21 echo $GLOBAL
22 printenv.py GLOBAL
23 export -n GLOBAL
24 echo $GLOBAL
25 printenv.py GLOBAL
26 ## STDOUT:
27 X
28 X
29 X
30 None
31 ## END
32 ## N-I mksh/dash STDOUT:
33 X
34 X
35 ## END
36 ## N-I mksh status: 1
37 ## N-I dash status: 2
38 ## N-I zsh STDOUT:
39 X
40 X
41 X
42 X
43 ## END
44
45 #### export -n undefined is ignored
46 set -o errexit
47 export -n undef
48 echo status=$?
49 ## stdout: status=0
50 ## N-I mksh/dash/zsh stdout-json: ""
51 ## N-I mksh status: 1
52 ## N-I dash status: 2
53 ## N-I zsh status: 1
54
55 #### export -n foo=bar not allowed
56 foo=old
57 export -n foo=new
58 echo status=$?
59 echo $foo
60 ## STDOUT:
61 status=2
62 old
63 ## END
64 ## OK bash STDOUT:
65 status=0
66 new
67 ## END
68 ## N-I zsh STDOUT:
69 status=1
70 old
71 ## END
72 ## N-I dash status: 2
73 ## N-I dash stdout-json: ""
74 ## N-I mksh status: 1
75 ## N-I mksh stdout-json: ""
76
77 #### Export a global variable and unset it
78 f() { export GLOBAL=X; }
79 f
80 echo $GLOBAL
81 printenv.py GLOBAL
82 unset GLOBAL
83 echo g=$GLOBAL
84 printenv.py GLOBAL
85 ## STDOUT:
86 X
87 X
88 g=
89 None
90 ## END
91
92 #### Export existing global variables
93 G1=g1
94 G2=g2
95 export G1 G2
96 printenv.py G1 G2
97 ## STDOUT:
98 g1
99 g2
100 ## END
101
102 #### Export existing local variable
103 f() {
104 local L1=local1
105 export L1
106 printenv.py L1
107 }
108 f
109 printenv.py L1
110 ## STDOUT:
111 local1
112 None
113 ## END
114
115 #### Export a local that shadows a global
116 V=global
117 f() {
118 local V=local1
119 export V
120 printenv.py V
121 }
122 f
123 printenv.py V # exported local out of scope; global isn't exported yet
124 export V
125 printenv.py V # now it's exported
126 ## STDOUT:
127 local1
128 None
129 global
130 ## END
131
132 #### Export a variable before defining it
133 export U
134 U=u
135 printenv.py U
136 ## stdout: u
137
138 #### Unset exported variable, then define it again. It's NOT still exported.
139 export U
140 U=u
141 printenv.py U
142 unset U
143 printenv.py U
144 U=newvalue
145 echo $U
146 printenv.py U
147 ## STDOUT:
148 u
149 None
150 newvalue
151 None
152 ## END
153
154 #### Exporting a parent func variable (dynamic scope)
155 # The algorithm is to walk up the stack and export that one.
156 inner() {
157 export outer_var
158 echo "inner: $outer_var"
159 printenv.py outer_var
160 }
161 outer() {
162 local outer_var=X
163 echo "before inner"
164 printenv.py outer_var
165 inner
166 echo "after inner"
167 printenv.py outer_var
168 }
169 outer
170 ## STDOUT:
171 before inner
172 None
173 inner: X
174 X
175 after inner
176 X
177 ## END
178
179 #### Dependent export setting
180 # FOO is not respected here either.
181 export FOO=foo v=$(printenv.py FOO)
182 echo "v=$v"
183 ## stdout: v=None
184
185 #### Exporting a variable doesn't change it
186 old=$PATH
187 export PATH
188 new=$PATH
189 test "$old" = "$new" && echo "not changed"
190 ## stdout: not changed
191
192 #### can't export array
193 typeset -a a
194 a=(1 2 3)
195 export a
196 printenv.py a
197 ## STDOUT:
198 None
199 ## END
200 ## BUG mksh STDOUT:
201 1
202 ## END
203 ## N-I dash status: 2
204 ## N-I dash stdout-json: ""
205 ## OK osh status: 1
206 ## OK osh stdout-json: ""
207
208 #### can't export associative array
209 typeset -A a
210 a["foo"]=bar
211 export a
212 printenv.py a
213 ## STDOUT:
214 None
215 ## END
216 ## N-I mksh status: 1
217 ## N-I mksh stdout-json: ""
218 ## OK osh status: 1
219 ## OK osh stdout-json: ""
220
221 #### assign to readonly variable
222 # bash doesn't abort unless errexit!
223 readonly foo=bar
224 foo=eggs
225 echo "status=$?" # nothing happens
226 ## status: 1
227 ## BUG bash stdout: status=1
228 ## BUG bash status: 0
229 ## OK dash/mksh status: 2
230
231 #### Make an existing local variable readonly
232 f() {
233 local x=local
234 readonly x
235 echo $x
236 eval 'x=bar' # Wrap in eval so it's not fatal
237 echo status=$?
238 }
239 x=global
240 f
241 echo $x
242 ## STDOUT:
243 local
244 status=1
245 global
246 ## END
247 ## OK dash STDOUT:
248 local
249 ## END
250 ## OK dash status: 2
251
252 # mksh aborts the function, weird
253 ## OK mksh STDOUT:
254 local
255 global
256 ## END
257
258 #### assign to readonly variable - errexit
259 set -o errexit
260 readonly foo=bar
261 foo=eggs
262 echo "status=$?" # nothing happens
263 ## status: 1
264 ## OK dash/mksh status: 2
265
266 #### Unset a variable
267 foo=bar
268 echo foo=$foo
269 unset foo
270 echo foo=$foo
271 ## STDOUT:
272 foo=bar
273 foo=
274 ## END
275
276 #### Unset exit status
277 V=123
278 unset V
279 echo status=$?
280 ## stdout: status=0
281
282 #### Unset nonexistent variable
283 unset ZZZ
284 echo status=$?
285 ## stdout: status=0
286
287 #### Unset readonly variable
288 # dash and zsh abort the whole program. OSH doesn't?
289 readonly R=foo
290 unset R
291 echo status=$?
292 ## status: 0
293 ## stdout: status=1
294 ## OK dash status: 2
295 ## OK dash stdout-json: ""
296 ## OK zsh status: 1
297 ## OK zsh stdout-json: ""
298
299 #### Unset a function without -f
300 f() {
301 echo foo
302 }
303 f
304 unset f
305 f
306 ## stdout: foo
307 ## status: 127
308 ## N-I dash/mksh/zsh status: 0
309 ## N-I dash/mksh/zsh STDOUT:
310 foo
311 foo
312 ## END
313
314 #### Unset has dynamic scope
315 f() {
316 unset foo
317 }
318 foo=bar
319 echo foo=$foo
320 f
321 echo foo=$foo
322 ## STDOUT:
323 foo=bar
324 foo=
325 ## END
326
327 #### Unset invalid variable name
328 unset %
329 echo status=$?
330 ## STDOUT:
331 status=2
332 ## END
333 ## OK bash/mksh STDOUT:
334 status=1
335 ## END
336 ## BUG zsh STDOUT:
337 status=0
338 ## END
339 # dash does a hard failure!
340 ## OK dash stdout-json: ""
341 ## OK dash status: 2
342
343 #### Unset nonexistent variable
344 unset _nonexistent__
345 echo status=$?
346 ## STDOUT:
347 status=0
348 ## END
349
350 #### Unset -v
351 foo() {
352 echo "function foo"
353 }
354 foo=bar
355 unset -v foo
356 echo foo=$foo
357 foo
358 ## STDOUT:
359 foo=
360 function foo
361 ## END
362
363 #### Unset -f
364 foo() {
365 echo "function foo"
366 }
367 foo=bar
368 unset -f foo
369 echo foo=$foo
370 foo
371 echo status=$?
372 ## STDOUT:
373 foo=bar
374 status=127
375 ## END
376
377 #### Unset array member
378 a=(x y z)
379 unset 'a[1]'
380 echo status=$?
381 echo "${a[@]}" len="${#a[@]}"
382 ## STDOUT:
383 status=0
384 x z len=2
385 ## END
386 ## N-I dash status: 2
387 ## N-I dash stdout-json: ""
388 ## OK zsh STDOUT:
389 status=0
390 y z len=3
391 ## END
392 ## N-I osh STDOUT:
393 status=2
394 x y z len=3
395 ## END
396
397 #### Unset array member with expression
398 i=1
399 a=(w x y z)
400 unset 'a[ i - 1 ]' a[i+1] # note: can't have space between a and [
401 echo status=$?
402 echo "${a[@]}" len="${#a[@]}"
403 ## STDOUT:
404 status=0
405 x z len=2
406 ## END
407 ## N-I dash status: 2
408 ## N-I dash stdout-json: ""
409 ## N-I zsh status: 1
410 ## N-I zsh stdout-json: ""
411 ## N-I osh STDOUT:
412 status=2
413 w x y z len=4
414 ## END
415
416 #### Use local twice
417 f() {
418 local foo=bar
419 local foo
420 echo $foo
421 }
422 f
423 ## stdout: bar
424 ## BUG zsh STDOUT:
425 foo=bar
426 bar
427 ## END
428
429 #### Local without variable is still unset!
430 set -o nounset
431 f() {
432 local foo
433 echo "[$foo]"
434 }
435 f
436 ## stdout-json: ""
437 ## status: 1
438 ## OK dash status: 2
439 # zsh doesn't support nounset?
440 ## BUG zsh stdout: []
441 ## BUG zsh status: 0
442
443 #### local after readonly
444 f() {
445 readonly y
446 local x=1 y=$(( x ))
447 echo y=$y
448 }
449 f
450 ## stdout-json: ""
451 ## status: 1
452 ## OK dash status: 2
453 ## BUG bash stdout: y=
454 ## BUG bash status: 0
455 ## BUG mksh stdout: y=0
456 ## BUG mksh status: 0
457