1 ## oils_failures_allowed: 1
2 ## compare_shells: dash bash 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
194 typeset -a a
195 a=(1 2 3)
196 export a
197 printenv.py a
198 ## STDOUT:
199 None
200 ## END
201 ## BUG mksh STDOUT:
202 1
203 ## END
204 ## N-I dash status: 2
205 ## N-I dash stdout-json: ""
206 ## OK osh status: 1
207 ## OK osh stdout-json: ""
208
209 #### can't export associative array
210 typeset -A a
211 a["foo"]=bar
212 export a
213 printenv.py a
214 ## STDOUT:
215 None
216 ## END
217 ## N-I mksh status: 1
218 ## N-I mksh stdout-json: ""
219 ## OK osh status: 1
220 ## OK osh stdout-json: ""
221
222 #### assign to readonly variable
223 # bash doesn't abort unless errexit!
224 readonly foo=bar
225 foo=eggs
226 echo "status=$?" # nothing happens
227 ## status: 1
228 ## BUG bash stdout: status=1
229 ## BUG bash status: 0
230 ## OK dash/mksh status: 2
231
232 #### Make an existing local variable readonly
233 f() {
234 local x=local
235 readonly x
236 echo $x
237 eval 'x=bar' # Wrap in eval so it's not fatal
238 echo status=$?
239 }
240 x=global
241 f
242 echo $x
243 ## STDOUT:
244 local
245 status=1
246 global
247 ## END
248 ## OK dash STDOUT:
249 local
250 ## END
251 ## OK dash status: 2
252
253 # mksh aborts the function, weird
254 ## OK mksh STDOUT:
255 local
256 global
257 ## END
258
259 #### assign to readonly variable - errexit
260 set -o errexit
261 readonly foo=bar
262 foo=eggs
263 echo "status=$?" # nothing happens
264 ## status: 1
265 ## OK dash/mksh status: 2
266
267 #### Unset a variable
268 foo=bar
269 echo foo=$foo
270 unset foo
271 echo foo=$foo
272 ## STDOUT:
273 foo=bar
274 foo=
275 ## END
276
277 #### Unset exit status
278 V=123
279 unset V
280 echo status=$?
281 ## stdout: status=0
282
283 #### Unset nonexistent variable
284 unset ZZZ
285 echo status=$?
286 ## stdout: status=0
287
288 #### Unset readonly variable
289 # dash and zsh abort the whole program. OSH doesn't?
290 readonly R=foo
291 unset R
292 echo status=$?
293 ## status: 0
294 ## stdout: status=1
295 ## OK dash status: 2
296 ## OK dash stdout-json: ""
297 ## OK zsh status: 1
298 ## OK zsh stdout-json: ""
299
300 #### Unset a function without -f
301 f() {
302 echo foo
303 }
304 f
305 unset f
306 f
307 ## stdout: foo
308 ## status: 127
309 ## N-I dash/mksh/zsh status: 0
310 ## N-I dash/mksh/zsh STDOUT:
311 foo
312 foo
313 ## END
314
315 #### Unset has dynamic scope
316 f() {
317 unset foo
318 }
319 foo=bar
320 echo foo=$foo
321 f
322 echo foo=$foo
323 ## STDOUT:
324 foo=bar
325 foo=
326 ## END
327
328 #### Unset and scope (bug #653)
329 unlocal() { unset "$@"; }
330
331 level2() {
332 local hello=yy
333
334 echo level2=$hello
335 unlocal hello
336 echo level2=$hello
337 }
338
339 level1() {
340 local hello=xx
341
342 level2
343
344 echo level1=$hello
345 unlocal hello
346 echo level1=$hello
347
348 level2
349 }
350
351 hello=global
352 level1
353
354 # bash, mksh, yash agree here.
355 ## STDOUT:
356 level2=yy
357 level2=xx
358 level1=xx
359 level1=global
360 level2=yy
361 level2=global
362 ## END
363 ## OK dash/ash/zsh STDOUT:
364 level2=yy
365 level2=
366 level1=xx
367 level1=
368 level2=yy
369 level2=
370 ## END
371
372 #### unset of local reveals variable in higher scope
373
374 # Oil has a RARE behavior here (matching yash and mksh), but at least it's
375 # consistent.
376
377 x=global
378 f() {
379 local x=foo
380 echo x=$x
381 unset x
382 echo x=$x
383 }
384 f
385 ## STDOUT:
386 x=foo
387 x=global
388 ## END
389 ## OK dash/bash/zsh/ash STDOUT:
390 x=foo
391 x=
392 ## END
393
394 #### Unset invalid variable name
395 unset %
396 echo status=$?
397 ## STDOUT:
398 status=2
399 ## END
400 ## OK bash/mksh STDOUT:
401 status=1
402 ## END
403 ## BUG zsh STDOUT:
404 status=0
405 ## END
406 # dash does a hard failure!
407 ## OK dash stdout-json: ""
408 ## OK dash status: 2
409
410 #### Unset nonexistent variable
411 unset _nonexistent__
412 echo status=$?
413 ## STDOUT:
414 status=0
415 ## END
416
417 #### Unset -v
418 foo() {
419 echo "function foo"
420 }
421 foo=bar
422 unset -v foo
423 echo foo=$foo
424 foo
425 ## STDOUT:
426 foo=
427 function foo
428 ## END
429
430 #### Unset -f
431 foo() {
432 echo "function foo"
433 }
434 foo=bar
435 unset -f foo
436 echo foo=$foo
437 foo
438 echo status=$?
439 ## STDOUT:
440 foo=bar
441 status=127
442 ## END
443
444 #### Unset array member
445 a=(x y z)
446 unset 'a[1]'
447 echo status=$?
448 echo "${a[@]}" len="${#a[@]}"
449 ## STDOUT:
450 status=0
451 x z len=2
452 ## END
453 ## N-I dash status: 2
454 ## N-I dash stdout-json: ""
455 ## OK zsh STDOUT:
456 status=0
457 y z len=3
458 ## END
459
460 #### Unset errors
461 unset undef
462 echo status=$?
463
464 a=(x y z)
465 unset 'a[99]' # out of range
466 echo status=$?
467
468 unset 'not_array[99]' # not an array
469 echo status=$?
470
471 ## STDOUT:
472 status=0
473 status=0
474 status=0
475 ## END
476 ## N-I dash status: 2
477 ## N-I dash STDOUT:
478 status=0
479 ## END
480
481 #### Unset wrong type
482 case $SH in (mksh) exit ;; esac
483
484 declare undef
485 unset -v 'undef[1]'
486 echo undef $?
487 unset -v 'undef["key"]'
488 echo undef $?
489
490 declare a=(one two)
491 unset -v 'a[1]'
492 echo array $?
493
494 #shopt -s strict_arith || true
495 # In Oil, the string 'key' is converted to an integer, which is 0, unless
496 # strict_arith is on, when it fails.
497 unset -v 'a["key"]'
498 echo array $?
499
500 declare -A A=(['key']=val)
501 unset -v 'A[1]'
502 echo assoc $?
503 unset -v 'A["key"]'
504 echo assoc $?
505
506 ## STDOUT:
507 undef 1
508 undef 1
509 array 0
510 array 1
511 assoc 0
512 assoc 0
513 ## END
514 ## OK osh STDOUT:
515 undef 1
516 undef 1
517 array 0
518 array 0
519 assoc 0
520 assoc 0
521 ## END
522 ## BUG zsh STDOUT:
523 undef 0
524 undef 1
525 array 0
526 array 1
527 assoc 0
528 assoc 0
529 ## END
530 ## N-I dash/mksh stdout-json: ""
531 ## N-I dash status: 2
532
533
534 #### unset -v assoc (related to issue #661)
535
536 case $SH in (dash|mksh|zsh) return; esac
537
538 declare -A dict=()
539 key=1],a[1
540 dict["$key"]=foo
541 echo ${#dict[@]}
542 echo keys=${!dict[@]}
543 echo vals=${dict[@]}
544
545 unset -v 'dict["$key"]'
546 echo ${#dict[@]}
547 echo keys=${!dict[@]}
548 echo vals=${dict[@]}
549 ## STDOUT:
550 1
551 keys=1],a[1
552 vals=foo
553 0
554 keys=
555 vals=
556 ## END
557 ## N-I dash/mksh/zsh stdout-json: ""
558
559 #### unset assoc errors
560
561 case $SH in (dash|mksh) return; esac
562
563 declare -A assoc=(['key']=value)
564 unset 'assoc["nonexistent"]'
565 echo status=$?
566
567 ## STDOUT:
568 status=0
569 ## END
570 ## N-I dash/mksh stdout-json: ""
571
572
573 #### Unset array member with dynamic parsing
574
575 i=1
576 a=(w x y z)
577 unset 'a[ i - 1 ]' a[i+1] # note: can't have space between a and [
578 echo status=$?
579 echo "${a[@]}" len="${#a[@]}"
580 ## STDOUT:
581 status=0
582 x z len=2
583 ## END
584 ## N-I dash status: 2
585 ## N-I dash stdout-json: ""
586 ## N-I zsh status: 1
587 ## N-I zsh stdout-json: ""
588
589 #### Use local twice
590 f() {
591 local foo=bar
592 local foo
593 echo $foo
594 }
595 f
596 ## stdout: bar
597 ## BUG zsh STDOUT:
598 foo=bar
599 bar
600 ## END
601
602 #### Local without variable is still unset!
603 set -o nounset
604 f() {
605 local foo
606 echo "[$foo]"
607 }
608 f
609 ## stdout-json: ""
610 ## status: 1
611 ## OK dash status: 2
612 # zsh doesn't support nounset?
613 ## BUG zsh stdout: []
614 ## BUG zsh status: 0
615
616 #### local after readonly
617 f() {
618 readonly y
619 local x=1 y=$(( x ))
620 echo y=$y
621 }
622 f
623 echo y=$y
624 ## status: 1
625 ## stdout-json: ""
626
627 ## OK dash status: 2
628
629 ## BUG mksh status: 0
630 ## BUG mksh STDOUT:
631 y=0
632 y=
633 ## END
634
635 ## BUG bash status: 0
636 ## BUG bash STDOUT:
637 y=
638 y=
639 ## END
640
641 #### unset a[-1] (bf.bash regression)
642 case $SH in (dash|zsh) exit ;; esac
643
644 a=(1 2 3)
645 unset a[-1]
646 echo len=${#a[@]}
647
648 echo last=${a[-1]}
649 (( last = a[-1] ))
650 echo last=$last
651
652 (( a[-1] = 42 ))
653 echo "${a[@]}"
654
655 ## STDOUT:
656 len=2
657 last=2
658 last=2
659 1 42
660 ## END
661 ## BUG mksh STDOUT:
662 len=3
663 last=
664 last=0
665 1 2 3 42
666 ## END
667 ## N-I dash/zsh stdout-json: ""
668
669
670 #### unset a[-1] in sparse array (bf.bash regression)
671 case $SH in (dash|zsh) exit ;; esac
672
673 a=(0 1 2 3 4)
674 unset a[1]
675 unset a[4]
676 echo len=${#a[@]} a=${a[@]}
677 echo last=${a[-1]} second=${a[-2]} third=${a[-3]}
678
679 echo ---
680 unset a[3]
681 echo len=${#a[@]} a=${a[@]}
682 echo last=${a[-1]} second=${a[-2]} third=${a[-3]}
683
684 ## STDOUT:
685 len=3 a=0 2 3
686 last=3 second=2 third=
687 ---
688 len=2 a=0 2
689 last=2 second= third=0
690 ## END
691
692 ## BUG mksh STDOUT:
693 len=3 a=0 2 3
694 last= second= third=
695 ---
696 len=2 a=0 2
697 last= second= third=
698 ## END
699
700 ## N-I dash/zsh stdout-json: ""
701