OILS / spec / builtin-vars.test.sh View on Github | oilshell.org

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