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 and scope (bug #653)
328 unlocal() { unset "$@"; }
329
330 level2() {
331 local hello=yy
332
333 echo level2=$hello
334 unlocal hello
335 echo level2=$hello
336 }
337
338 level1() {
339 local hello=xx
340
341 level2
342
343 echo level1=$hello
344 unlocal hello
345 echo level1=$hello
346
347 level2
348 }
349
350 hello=global
351 level1
352
353 # bash, mksh, yash agree here.
354 ## STDOUT:
355 level2=yy
356 level2=xx
357 level1=xx
358 level1=global
359 level2=yy
360 level2=global
361 ## END
362 ## OK dash/ash/zsh STDOUT:
363 level2=yy
364 level2=
365 level1=xx
366 level1=
367 level2=yy
368 level2=
369 ## END
370
371 #### unset of local reveals variable in higher scope
372
373 # Oil has a RARE behavior here (matching yash and mksh), but at least it's
374 # consistent.
375
376 x=global
377 f() {
378 local x=foo
379 echo x=$x
380 unset x
381 echo x=$x
382 }
383 f
384 ## STDOUT:
385 x=foo
386 x=global
387 ## END
388 ## OK dash/bash/zsh/ash STDOUT:
389 x=foo
390 x=
391 ## END
392
393 #### Unset invalid variable name
394 unset %
395 echo status=$?
396 ## STDOUT:
397 status=2
398 ## END
399 ## OK bash/mksh STDOUT:
400 status=1
401 ## END
402 ## BUG zsh STDOUT:
403 status=0
404 ## END
405 # dash does a hard failure!
406 ## OK dash stdout-json: ""
407 ## OK dash status: 2
408
409 #### Unset nonexistent variable
410 unset _nonexistent__
411 echo status=$?
412 ## STDOUT:
413 status=0
414 ## END
415
416 #### Unset -v
417 foo() {
418 echo "function foo"
419 }
420 foo=bar
421 unset -v foo
422 echo foo=$foo
423 foo
424 ## STDOUT:
425 foo=
426 function foo
427 ## END
428
429 #### Unset -f
430 foo() {
431 echo "function foo"
432 }
433 foo=bar
434 unset -f foo
435 echo foo=$foo
436 foo
437 echo status=$?
438 ## STDOUT:
439 foo=bar
440 status=127
441 ## END
442
443 #### Unset array member
444 shopt -s eval_unsafe_arith
445
446 a=(x y z)
447 unset 'a[1]'
448 echo status=$?
449 echo "${a[@]}" len="${#a[@]}"
450 ## STDOUT:
451 status=0
452 x z len=2
453 ## END
454 ## N-I dash status: 2
455 ## N-I dash stdout-json: ""
456 ## OK zsh STDOUT:
457 status=0
458 y z len=3
459 ## END
460
461 #### Unset errors
462 shopt -s eval_unsafe_arith
463
464 unset undef
465 echo status=$?
466
467 a=(x y z)
468 unset 'a[99]' # out of range
469 echo status=$?
470
471 unset 'not_array[99]' # not an array
472 echo status=$?
473
474 ## STDOUT:
475 status=0
476 status=0
477 status=0
478 ## END
479 ## N-I dash status: 2
480 ## N-I dash STDOUT:
481 status=0
482 ## END
483
484 #### Unset wrong type
485 case $SH in (mksh) exit ;; esac
486
487 shopt -s eval_unsafe_arith || true
488
489 declare undef
490 unset -v 'undef[1]'
491 echo undef $?
492 unset -v 'undef["key"]'
493 echo undef $?
494
495 declare a=(one two)
496 unset -v 'a[1]'
497 echo array $?
498
499 #shopt -s strict_arith || true
500 # In Oil, the string 'key' is converted to an integer, which is 0, unless
501 # strict_arith is on, when it fails.
502 unset -v 'a["key"]'
503 echo array $?
504
505 declare -A A=(['key']=val)
506 unset -v 'A[1]'
507 echo assoc $?
508 unset -v 'A["key"]'
509 echo assoc $?
510
511 ## STDOUT:
512 undef 1
513 undef 1
514 array 0
515 array 1
516 assoc 0
517 assoc 0
518 ## END
519 ## OK osh STDOUT:
520 undef 1
521 undef 1
522 array 0
523 array 0
524 assoc 0
525 assoc 0
526 ## END
527 ## BUG zsh STDOUT:
528 undef 0
529 undef 1
530 array 0
531 array 1
532 assoc 0
533 assoc 0
534 ## END
535 ## N-I dash/mksh stdout-json: ""
536 ## N-I dash status: 2
537
538
539 #### unset -v assoc (related to issue #661)
540 shopt -s eval_unsafe_arith || true
541
542 case $SH in (dash|mksh|zsh) return; esac
543
544 declare -A dict=()
545 key=1],a[1
546 dict["$key"]=foo
547 echo ${#dict[@]}
548 echo keys=${!dict[@]}
549 echo vals=${dict[@]}
550
551 unset -v 'dict["$key"]'
552 echo ${#dict[@]}
553 echo keys=${!dict[@]}
554 echo vals=${dict[@]}
555 ## STDOUT:
556 1
557 keys=1],a[1
558 vals=foo
559 0
560 keys=
561 vals=
562 ## END
563 ## N-I dash/mksh/zsh stdout-json: ""
564
565 #### unset assoc errors
566 shopt -s eval_unsafe_arith || true
567
568 case $SH in (dash|mksh) return; esac
569
570 declare -A assoc=(['key']=value)
571 unset 'assoc["nonexistent"]'
572 echo status=$?
573
574 ## STDOUT:
575 status=0
576 ## END
577 ## N-I dash/mksh stdout-json: ""
578
579
580 #### Unset array member with dynamic parsing
581 shopt -s eval_unsafe_arith
582
583 i=1
584 a=(w x y z)
585 unset 'a[ i - 1 ]' a[i+1] # note: can't have space between a and [
586 echo status=$?
587 echo "${a[@]}" len="${#a[@]}"
588 ## STDOUT:
589 status=0
590 x z len=2
591 ## END
592 ## N-I dash status: 2
593 ## N-I dash stdout-json: ""
594 ## N-I zsh status: 1
595 ## N-I zsh stdout-json: ""
596
597 #### Use local twice
598 f() {
599 local foo=bar
600 local foo
601 echo $foo
602 }
603 f
604 ## stdout: bar
605 ## BUG zsh STDOUT:
606 foo=bar
607 bar
608 ## END
609
610 #### Local without variable is still unset!
611 set -o nounset
612 f() {
613 local foo
614 echo "[$foo]"
615 }
616 f
617 ## stdout-json: ""
618 ## status: 1
619 ## OK dash status: 2
620 # zsh doesn't support nounset?
621 ## BUG zsh stdout: []
622 ## BUG zsh status: 0
623
624 #### local after readonly
625 f() {
626 readonly y
627 local x=1 y=$(( x ))
628 echo y=$y
629 }
630 f
631 echo y=$y
632 ## status: 1
633 ## stdout-json: ""
634
635 ## OK dash status: 2
636
637 ## BUG mksh status: 0
638 ## BUG mksh STDOUT:
639 y=0
640 y=
641 ## END
642
643 ## BUG bash status: 0
644 ## BUG bash STDOUT:
645 y=
646 y=
647 ## END
648
649 #### unset a[-1] (bf.bash regression)
650 case $SH in (dash|zsh) exit ;; esac
651
652 shopt -s eval_unsafe_arith
653 a=(1 2 3)
654 unset a[-1]
655 echo len=${#a[@]}
656
657 echo last=${a[-1]}
658 (( last = a[-1] ))
659 echo last=$last
660
661 (( a[-1] = 42 ))
662 echo "${a[@]}"
663
664 ## STDOUT:
665 len=2
666 last=2
667 last=2
668 1 42
669 ## END
670 ## BUG mksh STDOUT:
671 len=3
672 last=
673 last=0
674 1 2 3 42
675 ## END
676 ## N-I dash/zsh stdout-json: ""
677
678
679 #### unset a[-1] in sparse array (bf.bash regression)
680 case $SH in (dash|zsh) exit ;; esac
681
682 shopt -s eval_unsafe_arith
683 a=(0 1 2 3 4)
684 unset a[1]
685 unset a[4]
686 echo len=${#a[@]} a=${a[@]}
687 echo last=${a[-1]} second=${a[-2]} third=${a[-3]}
688
689 echo ---
690 unset a[3]
691 echo len=${#a[@]} a=${a[@]}
692 echo last=${a[-1]} second=${a[-2]} third=${a[-3]}
693
694 ## STDOUT:
695 len=3 a=0 2 3
696 last=3 second=2 third=
697 ---
698 len=2 a=0 2
699 last=2 second= third=0
700 ## END
701
702 ## BUG mksh STDOUT:
703 len=3 a=0 2 3
704 last= second= third=
705 ---
706 len=2 a=0 2
707 last= second= third=
708 ## END
709
710 ## N-I dash/zsh stdout-json: ""