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 |
|