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