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