1 #
2 # Tests for builtins having to do with variables: export, readonly, unset, etc.
3 #
4 # Also see assign.test.sh.
5
6 #### Export sets a global variable
7 # Even after you do export -n, it still exists.
8 f() { export GLOBAL=X; }
9 f
10 echo $GLOBAL
11 printenv.py GLOBAL
12 ## STDOUT:
13 X
14 X
15 ## END
16
17 #### Export sets a global variable that persists after export -n
18 f() { export GLOBAL=X; }
19 f
20 echo $GLOBAL
21 printenv.py GLOBAL
22 export -n GLOBAL
23 echo $GLOBAL
24 printenv.py GLOBAL
25 ## STDOUT:
26 X
27 X
28 X
29 None
30 ## END
31 ## N-I mksh/dash STDOUT:
32 X
33 X
34 ## END
35 ## N-I mksh status: 1
36 ## N-I dash status: 2
37 ## N-I zsh STDOUT:
38 X
39 X
40 X
41 X
42 ## END
43
44 #### export -n undefined is ignored
45 set -o errexit
46 export -n undef
47 echo status=$?
48 ## stdout: status=0
49 ## N-I mksh/dash/zsh stdout-json: ""
50 ## N-I mksh status: 1
51 ## N-I dash status: 2
52 ## N-I zsh status: 1
53
54 #### export -n foo=bar not allowed
55 foo=old
56 export -n foo=new
57 echo status=$?
58 echo $foo
59 ## STDOUT:
60 status=2
61 old
62 ## END
63 ## OK bash STDOUT:
64 status=0
65 new
66 ## END
67 ## N-I zsh STDOUT:
68 status=1
69 old
70 ## END
71 ## N-I dash status: 2
72 ## N-I dash stdout-json: ""
73 ## N-I mksh status: 1
74 ## N-I mksh stdout-json: ""
75
76 #### Export a global variable and unset it
77 f() { export GLOBAL=X; }
78 f
79 echo $GLOBAL
80 printenv.py GLOBAL
81 unset GLOBAL
82 echo g=$GLOBAL
83 printenv.py GLOBAL
84 ## STDOUT:
85 X
86 X
87 g=
88 None
89 ## END
90
91 #### Export existing global variables
92 G1=g1
93 G2=g2
94 export G1 G2
95 printenv.py G1 G2
96 ## STDOUT:
97 g1
98 g2
99 ## END
100
101 #### Export existing local variable
102 f() {
103 local L1=local1
104 export L1
105 printenv.py L1
106 }
107 f
108 printenv.py L1
109 ## STDOUT:
110 local1
111 None
112 ## END
113
114 #### Export a local that shadows a global
115 V=global
116 f() {
117 local V=local1
118 export V
119 printenv.py V
120 }
121 f
122 printenv.py V # exported local out of scope; global isn't exported yet
123 export V
124 printenv.py V # now it's exported
125 ## STDOUT:
126 local1
127 None
128 global
129 ## END
130
131 #### Export a variable before defining it
132 export U
133 U=u
134 printenv.py U
135 ## stdout: u
136
137 #### Unset exported variable, then define it again. It's NOT still exported.
138 export U
139 U=u
140 printenv.py U
141 unset U
142 printenv.py U
143 U=newvalue
144 echo $U
145 printenv.py U
146 ## STDOUT:
147 u
148 None
149 newvalue
150 None
151 ## END
152
153 #### Exporting a parent func variable (dynamic scope)
154 # The algorithm is to walk up the stack and export that one.
155 inner() {
156 export outer_var
157 echo "inner: $outer_var"
158 printenv.py outer_var
159 }
160 outer() {
161 local outer_var=X
162 echo "before inner"
163 printenv.py outer_var
164 inner
165 echo "after inner"
166 printenv.py outer_var
167 }
168 outer
169 ## STDOUT:
170 before inner
171 None
172 inner: X
173 X
174 after inner
175 X
176 ## END
177
178 #### Dependent export setting
179 # FOO is not respected here either.
180 export FOO=foo v=$(printenv.py FOO)
181 echo "v=$v"
182 ## stdout: v=None
183
184 #### Exporting a variable doesn't change it
185 old=$PATH
186 export PATH
187 new=$PATH
188 test "$old" = "$new" && echo "not changed"
189 ## stdout: not changed
190
191 #### can't export array
192 typeset -a a
193 a=(1 2 3)
194 export a
195 printenv.py a
196 ## STDOUT:
197 None
198 ## END
199 ## BUG mksh STDOUT:
200 1
201 ## END
202 ## N-I dash status: 2
203 ## N-I dash stdout-json: ""
204 ## OK osh status: 1
205 ## OK osh stdout-json: ""
206
207 #### can't export associative array
208 typeset -A a
209 a["foo"]=bar
210 export a
211 printenv.py a
212 ## STDOUT:
213 None
214 ## END
215 ## N-I mksh status: 1
216 ## N-I mksh stdout-json: ""
217 ## OK osh status: 1
218 ## OK osh stdout-json: ""
219
220 #### assign to readonly variable
221 # bash doesn't abort unless errexit!
222 readonly foo=bar
223 foo=eggs
224 echo "status=$?" # nothing happens
225 ## status: 1
226 ## BUG bash stdout: status=1
227 ## BUG bash status: 0
228 ## OK dash/mksh status: 2
229
230 #### Make an existing local variable readonly
231 f() {
232 local x=local
233 readonly x
234 echo $x
235 eval 'x=bar' # Wrap in eval so it's not fatal
236 echo status=$?
237 }
238 x=global
239 f
240 echo $x
241 ## STDOUT:
242 local
243 status=1
244 global
245 ## END
246 ## OK dash STDOUT:
247 local
248 ## END
249 ## OK dash status: 2
250
251 # mksh aborts the function, weird
252 ## OK mksh STDOUT:
253 local
254 global
255 ## END
256
257 #### assign to readonly variable - errexit
258 set -o errexit
259 readonly foo=bar
260 foo=eggs
261 echo "status=$?" # nothing happens
262 ## status: 1
263 ## OK dash/mksh status: 2
264
265 #### Unset a variable
266 foo=bar
267 echo foo=$foo
268 unset foo
269 echo foo=$foo
270 ## STDOUT:
271 foo=bar
272 foo=
273 ## END
274
275 #### Unset exit status
276 V=123
277 unset V
278 echo status=$?
279 ## stdout: status=0
280
281 #### Unset nonexistent variable
282 unset ZZZ
283 echo status=$?
284 ## stdout: status=0
285
286 #### Unset readonly variable
287 # dash and zsh abort the whole program. OSH doesn't?
288 readonly R=foo
289 unset R
290 echo status=$?
291 ## status: 0
292 ## stdout: status=1
293 ## OK dash status: 2
294 ## OK dash stdout-json: ""
295 ## OK zsh status: 1
296 ## OK zsh stdout-json: ""
297
298 #### Unset a function without -f
299 f() {
300 echo foo
301 }
302 f
303 unset f
304 f
305 ## stdout: foo
306 ## status: 127
307 ## N-I dash/mksh/zsh status: 0
308 ## N-I dash/mksh/zsh STDOUT:
309 foo
310 foo
311 ## END
312
313 #### Unset has dynamic scope
314 f() {
315 unset foo
316 }
317 foo=bar
318 echo foo=$foo
319 f
320 echo foo=$foo
321 ## STDOUT:
322 foo=bar
323 foo=
324 ## END
325
326 #### Unset and scope (bug #653)
327 unlocal() { unset "$@"; }
328
329 level2() {
330 local hello=yy
331
332 echo level2=$hello
333 unlocal hello
334 echo level2=$hello
335 }
336
337 level1() {
338 local hello=xx
339
340 level2
341
342 echo level1=$hello
343 unlocal hello
344 echo level1=$hello
345
346 level2
347 }
348
349 hello=global
350 level1
351
352 # bash, mksh, yash agree here.
353 ## STDOUT:
354 level2=yy
355 level2=xx
356 level1=xx
357 level1=global
358 level2=yy
359 level2=global
360 ## END
361 ## OK dash/ash/zsh STDOUT:
362 level2=yy
363 level2=
364 level1=xx
365 level1=
366 level2=yy
367 level2=
368 ## END
369
370 #### unset of local reveals variable in higher scope
371
372 # Oil has a RARE behavior here (matching yash and mksh), but at least it's
373 # consistent.
374
375 x=global
376 f() {
377 local x=foo
378 echo x=$x
379 unset x
380 echo x=$x
381 }
382 f
383 ## STDOUT:
384 x=foo
385 x=global
386 ## END
387 ## OK dash/bash/zsh/ash STDOUT:
388 x=foo
389 x=
390 ## END
391
392 #### Unset invalid variable name
393 unset %
394 echo status=$?
395 ## STDOUT:
396 status=2
397 ## END
398 ## OK bash/mksh STDOUT:
399 status=1
400 ## END
401 ## BUG zsh STDOUT:
402 status=0
403 ## END
404 # dash does a hard failure!
405 ## OK dash stdout-json: ""
406 ## OK dash status: 2
407
408 #### Unset nonexistent variable
409 unset _nonexistent__
410 echo status=$?
411 ## STDOUT:
412 status=0
413 ## END
414
415 #### Unset -v
416 foo() {
417 echo "function foo"
418 }
419 foo=bar
420 unset -v foo
421 echo foo=$foo
422 foo
423 ## STDOUT:
424 foo=
425 function foo
426 ## END
427
428 #### Unset -f
429 foo() {
430 echo "function foo"
431 }
432 foo=bar
433 unset -f foo
434 echo foo=$foo
435 foo
436 echo status=$?
437 ## STDOUT:
438 foo=bar
439 status=127
440 ## END
441
442 #### Unset array member
443 a=(x y z)
444 unset 'a[1]'
445 echo status=$?
446 echo "${a[@]}" len="${#a[@]}"
447 ## STDOUT:
448 status=0
449 x z len=2
450 ## END
451 ## N-I dash status: 2
452 ## N-I dash stdout-json: ""
453 ## OK zsh STDOUT:
454 status=0
455 y z len=3
456 ## END
457
458 #### Unset errors
459 unset undef
460 echo status=$?
461
462 a=(x y z)
463 unset 'a[99]' # out of range
464 echo status=$?
465
466 unset 'not_array[99]' # not an array
467 echo status=$?
468
469 ## STDOUT:
470 status=0
471 status=0
472 status=0
473 ## END
474 ## N-I dash status: 2
475 ## N-I dash STDOUT:
476 status=0
477 ## END
478
479 #### Unset wrong type
480 case $SH in (mksh) exit ;; esac
481
482 declare undef
483 unset -v 'undef[1]'
484 echo undef $?
485 unset -v 'undef["key"]'
486 echo undef $?
487
488 declare a=(one two)
489 unset -v 'a[1]'
490 echo array $?
491
492 #shopt -s strict_arith || true
493 # In Oil, the string 'key' is converted to an integer, which is 0, unless
494 # strict_arith is on, when it fails.
495 unset -v 'a["key"]'
496 echo array $?
497
498 declare -A A=(['key']=val)
499 unset -v 'A[1]'
500 echo assoc $?
501 unset -v 'A["key"]'
502 echo assoc $?
503
504 ## STDOUT:
505 undef 1
506 undef 1
507 array 0
508 array 1
509 assoc 0
510 assoc 0
511 ## END
512 ## OK osh STDOUT:
513 undef 1
514 undef 1
515 array 0
516 array 0
517 assoc 0
518 assoc 0
519 ## END
520 ## BUG zsh STDOUT:
521 undef 0
522 undef 1
523 array 0
524 array 1
525 assoc 0
526 assoc 0
527 ## END
528 ## N-I dash/mksh stdout-json: ""
529 ## N-I dash status: 2
530
531
532 #### unset -v assoc (related to issue #661)
533
534 case $SH in (dash|mksh|zsh) return; esac
535
536 declare -A dict=()
537 key=1],a[1
538 dict["$key"]=foo
539 echo ${#dict[@]}
540 echo keys=${!dict[@]}
541 echo vals=${dict[@]}
542
543 unset -v 'dict["$key"]'
544 echo ${#dict[@]}
545 echo keys=${!dict[@]}
546 echo vals=${dict[@]}
547 ## STDOUT:
548 1
549 keys=1],a[1
550 vals=foo
551 0
552 keys=
553 vals=
554 ## END
555 ## N-I dash/mksh/zsh stdout-json: ""
556
557 #### unset assoc errors
558
559 case $SH in (dash|mksh) return; esac
560
561 declare -A assoc=(['key']=value)
562 unset 'assoc["nonexistent"]'
563 echo status=$?
564
565 ## STDOUT:
566 status=0
567 ## END
568 ## N-I dash/mksh stdout-json: ""
569
570
571 #### Unset array member with dynamic parsing
572
573 i=1
574 a=(w x y z)
575 unset 'a[ i - 1 ]' a[i+1] # note: can't have space between a and [
576 echo status=$?
577 echo "${a[@]}" len="${#a[@]}"
578 ## STDOUT:
579 status=0
580 x z len=2
581 ## END
582 ## N-I dash status: 2
583 ## N-I dash stdout-json: ""
584 ## N-I zsh status: 1
585 ## N-I zsh stdout-json: ""
586
587 #### Use local twice
588 f() {
589 local foo=bar
590 local foo
591 echo $foo
592 }
593 f
594 ## stdout: bar
595 ## BUG zsh STDOUT:
596 foo=bar
597 bar
598 ## END
599
600 #### Local without variable is still unset!
601 set -o nounset
602 f() {
603 local foo
604 echo "[$foo]"
605 }
606 f
607 ## stdout-json: ""
608 ## status: 1
609 ## OK dash status: 2
610 # zsh doesn't support nounset?
611 ## BUG zsh stdout: []
612 ## BUG zsh status: 0
613
614 #### local after readonly
615 f() {
616 readonly y
617 local x=1 y=$(( x ))
618 echo y=$y
619 }
620 f
621 echo y=$y
622 ## status: 1
623 ## stdout-json: ""
624
625 ## OK dash status: 2
626
627 ## BUG mksh status: 0
628 ## BUG mksh STDOUT:
629 y=0
630 y=
631 ## END
632
633 ## BUG bash status: 0
634 ## BUG bash STDOUT:
635 y=
636 y=
637 ## END
638
639 #### unset a[-1] (bf.bash regression)
640 case $SH in (dash|zsh) exit ;; esac
641
642 a=(1 2 3)
643 unset a[-1]
644 echo len=${#a[@]}
645
646 echo last=${a[-1]}
647 (( last = a[-1] ))
648 echo last=$last
649
650 (( a[-1] = 42 ))
651 echo "${a[@]}"
652
653 ## STDOUT:
654 len=2
655 last=2
656 last=2
657 1 42
658 ## END
659 ## BUG mksh STDOUT:
660 len=3
661 last=
662 last=0
663 1 2 3 42
664 ## END
665 ## N-I dash/zsh stdout-json: ""
666
667
668 #### unset a[-1] in sparse array (bf.bash regression)
669 case $SH in (dash|zsh) exit ;; esac
670
671 a=(0 1 2 3 4)
672 unset a[1]
673 unset a[4]
674 echo len=${#a[@]} a=${a[@]}
675 echo last=${a[-1]} second=${a[-2]} third=${a[-3]}
676
677 echo ---
678 unset a[3]
679 echo len=${#a[@]} a=${a[@]}
680 echo last=${a[-1]} second=${a[-2]} third=${a[-3]}
681
682 ## STDOUT:
683 len=3 a=0 2 3
684 last=3 second=2 third=
685 ---
686 len=2 a=0 2
687 last=2 second= third=0
688 ## END
689
690 ## BUG mksh STDOUT:
691 len=3 a=0 2 3
692 last= second= third=
693 ---
694 len=2 a=0 2
695 last= second= third=
696 ## END
697
698 ## N-I dash/zsh stdout-json: ""
699