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

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