1 #!/usr/bin/env bash
2
3 #### Env value doesn't persist
4 FOO=foo printenv.py FOO
5 echo -$FOO-
6 ## STDOUT:
7 foo
8 --
9 ## END
10
11 #### Env value with equals
12 FOO=foo=foo printenv.py FOO
13 ## stdout: foo=foo
14
15 #### Env binding can use preceding bindings, but not subsequent ones
16 # This means that for ASSIGNMENT_WORD, on the RHS you invoke the parser again!
17 # Could be any kind of quoted string.
18 FOO="foo" BAR="[$FOO][$BAZ]" BAZ=baz printenv.py FOO BAR BAZ
19 ## STDOUT:
20 foo
21 [foo][]
22 baz
23 ## BUG mksh STDOUT:
24 foo
25 [][]
26 baz
27 ## END
28
29 #### Env value with two quotes
30 FOO='foo'"adjacent" printenv.py FOO
31 ## stdout: fooadjacent
32
33 #### Env value with escaped <
34 FOO=foo\<foo printenv.py FOO
35 ## stdout: foo<foo
36
37 #### FOO=foo echo [foo]
38 FOO=foo echo "[$foo]"
39 ## stdout: []
40
41 #### FOO=foo fun
42 fun() {
43 echo "[$FOO]"
44 }
45 FOO=foo fun
46 ## stdout: [foo]
47
48 #### Multiple temporary envs on the stack
49 g() {
50 echo "$F" "$G1" "$G2"
51 echo '--- g() ---'
52 P=p printenv.py F G1 G2 A P
53 }
54 f() {
55 # NOTE: G1 doesn't pick up binding f, but G2 picks up a.
56 # I don't quite understand why this is, but bash and OSH agree!
57 G1=[$f] G2=[$a] g
58 echo '--- f() ---'
59 printenv.py F G1 G2 A P
60 }
61 a=A
62 F=f f
63 ## STDOUT:
64 f [] [A]
65 --- g() ---
66 f
67 []
68 [A]
69 None
70 p
71 --- f() ---
72 f
73 None
74 None
75 None
76 None
77 ## END
78 ## OK mksh STDOUT:
79 # G1 and G2 somehow persist. I think that is a bug. They should be local to
80 # the G call.
81 f [] [A]
82 --- g() ---
83 f
84 []
85 [A]
86 None
87 p
88 --- f() ---
89 f
90 []
91 [A]
92 None
93 None
94 ## END
95 ## BUG dash STDOUT:
96 # dash sets even less stuff. Doesn't appear correct.
97 f [] [A]
98 --- g() ---
99 None
100 None
101 None
102 None
103 p
104 --- f() ---
105 None
106 None
107 None
108 None
109 None
110 ## END
111
112 #### Escaped = in command name
113 # foo=bar is in the 'spec/bin' dir.
114 foo\=bar
115 ## stdout: HI
116
117 #### Env binding not allowed before compound command
118 # bash gives exit code 2 for syntax error, because of 'do'.
119 # dash gives 0 because there is stuff after for? Should really give an error.
120 # mksh gives acceptable error of 1.
121 FOO=bar for i in a b; do printenv.py $FOO; done
122 ## status: 2
123 ## OK mksh/zsh status: 1
124
125 #### Trying to run keyword 'for'
126 FOO=bar for
127 ## status: 127
128 ## OK zsh status: 1
129
130 #### Empty env binding
131 EMPTY= printenv.py EMPTY
132 ## stdout:
133
134 #### Assignment doesn't do word splitting
135 words='one two'
136 a=$words
137 argv.py "$a"
138 ## stdout: ['one two']
139
140 #### Assignment doesn't do glob expansion
141 touch _tmp/z.Z _tmp/zz.Z
142 a=_tmp/*.Z
143 argv.py "$a"
144 ## stdout: ['_tmp/*.Z']
145
146 #### Env binding in readonly/declare is NOT exported! (pitfall)
147
148 # All shells agree on this, but it's very confusing behavior.
149 FOO=foo readonly v=$(printenv.py FOO)
150 echo "v=$v"
151
152 # bash has probems here:
153 FOO=foo readonly v2=$FOO
154 echo "v2=$v2"
155
156 ## STDOUT:
157 v=None
158 v2=foo
159 ## END
160 ## BUG bash STDOUT:
161 v=None
162 v2=
163 ## END
164
165 #### assignments / array assignments not interpreted after 'echo'
166 a=1 echo b[0]=2 c=3
167 ## stdout: b[0]=2 c=3
168 # zsh interprets [0] as some kind of glob
169 ## OK zsh stdout-json: ""
170 ## OK zsh status: 1
171
172 #### dynamic local variables (and splitting)
173 f() {
174 local "$1" # Only x is assigned here
175 echo x=\'$x\'
176 echo a=\'$a\'
177
178 local $1 # x and a are assigned here
179 echo x=\'$x\'
180 echo a=\'$a\'
181 }
182 f 'x=y a=b'
183 ## OK dash/bash/mksh STDOUT:
184 x='y a=b'
185 a=''
186 x='y'
187 a='b'
188 ## END
189 # osh and zsh don't do word splitting
190 ## STDOUT:
191 x='y a=b'
192 a=''
193 x='y a=b'
194 a=''
195 ## END
196
197 #### readonly x= gives empty string (regression)
198 readonly x=
199 argv.py "$x"
200 ## STDOUT:
201 ['']
202 ## END
203
204 #### 'local x' does not set variable
205 set -o nounset
206 f() {
207 local x
208 echo $x
209 }
210 f
211 ## status: 1
212 ## OK dash status: 2
213 ## BUG zsh status: 0
214
215 #### 'local -a x' does not set variable
216 set -o nounset
217 f() {
218 local -a x
219 echo $x
220 }
221 f
222 ## status: 1
223 ## OK dash status: 2
224 ## BUG zsh status: 0
225
226 #### 'local x' and then array assignment
227 f() {
228 local x
229 x[3]=foo
230 echo ${x[3]}
231 }
232 f
233 ## status: 0
234 ## stdout: foo
235 ## N-I dash status: 2
236 ## N-I dash stdout-json: ""
237 ## BUG zsh stdout: o
238
239 #### 'declare -A' and then dict assignment
240 declare -A foo
241 key=bar
242 foo["$key"]=value
243 echo ${foo["bar"]}
244 ## status: 0
245 ## stdout: value
246 ## N-I dash status: 2
247 ## N-I dash stdout-json: ""
248 ## N-I mksh status: 1
249 ## N-I mksh stdout-json: ""
250
251 #### declare in an if statement
252 # bug caught by my feature detection snippet in bash-completion
253 if ! foo=bar; then
254 echo BAD
255 fi
256 echo $foo
257 if ! eval 'spam=eggs'; then
258 echo BAD
259 fi
260 echo $spam
261 ## STDOUT:
262 bar
263 eggs
264 ## END
265
266
267 #### Modify a temporary binding
268 # (regression for bug found by Michael Greenberg)
269 f() {
270 echo "x before = $x"
271 x=$((x+1))
272 echo "x after = $x"
273 }
274 x=5 f
275 ## STDOUT:
276 x before = 5
277 x after = 6
278 ## END
279
280 #### Reveal existence of "temp frame" (All shells disagree here!!!)
281 f() {
282 echo "x=$x"
283
284 x=mutated-temp # mutate temp frame
285 echo "x=$x"
286
287 # Declare a new local
288 local x='local'
289 echo "x=$x"
290
291 # Unset it
292 unset x
293 echo "x=$x"
294 }
295
296 x=global
297 x=temp-binding f
298 echo "x=$x"
299
300 ## STDOUT:
301 x=temp-binding
302 x=mutated-temp
303 x=local
304 x=mutated-temp
305 x=global
306 ## END
307 ## OK dash/zsh STDOUT:
308 x=temp-binding
309 x=mutated-temp
310 x=local
311 x=
312 x=global
313 ## END
314 ## BUG bash STDOUT:
315 x=temp-binding
316 x=mutated-temp
317 x=local
318 x=global
319 x=global
320 ## END
321 ## BUG mksh STDOUT:
322 x=temp-binding
323 x=mutated-temp
324 x=local
325 x=mutated-temp
326 x=mutated-temp
327 ## END
328 ## BUG yash STDOUT:
329 # yash has no locals
330 x=temp-binding
331 x=mutated-temp
332 x=mutated-temp
333 x=
334 x=
335 ## END
336
337 #### Test above without 'local' (which is not POSIX)
338 f() {
339 echo "x=$x"
340
341 x=mutated-temp # mutate temp frame
342 echo "x=$x"
343
344 # Unset it
345 unset x
346 echo "x=$x"
347 }
348
349 x=global
350 x=temp-binding f
351 echo "x=$x"
352
353 ## OK dash/zsh STDOUT:
354 x=temp-binding
355 x=mutated-temp
356 x=
357 x=global
358 ## END
359 ## BUG mksh/yash STDOUT:
360 x=temp-binding
361 x=mutated-temp
362 x=
363 x=
364 ## END
365 ## STDOUT:
366 x=temp-binding
367 x=mutated-temp
368 x=global
369 x=global
370 ## END
371
372 #### Using ${x-default} after unsetting local shadowing a global
373 f() {
374 echo "x=$x"
375 local x='local'
376 echo "x=$x"
377 unset x
378 echo "- operator = ${x-default}"
379 echo ":- operator = ${x:-default}"
380 }
381 x=global
382 f
383 ## OK dash/bash/zsh STDOUT:
384 x=global
385 x=local
386 - operator = default
387 :- operator = default
388 ## END
389 ## STDOUT:
390 x=global
391 x=local
392 - operator = global
393 :- operator = global
394 ## END
395
396 #### Using ${x-default} after unsetting a temp binding shadowing a global
397 f() {
398 echo "x=$x"
399 local x='local'
400 echo "x=$x"
401 unset x
402 echo "- operator = ${x-default}"
403 echo ":- operator = ${x:-default}"
404 }
405 x=global
406 x=temp-binding f
407 ## OK dash/zsh STDOUT:
408 x=temp-binding
409 x=local
410 - operator = default
411 :- operator = default
412 ## END
413 ## STDOUT:
414 x=temp-binding
415 x=local
416 - operator = temp-binding
417 :- operator = temp-binding
418 ## END
419 ## BUG bash STDOUT:
420 x=temp-binding
421 x=local
422 - operator = global
423 :- operator = global
424 ## END
425
426 #### static assignment doesn't split
427 words='a b c'
428 export ex=$words
429 glo=$words
430 readonly ro=$words
431 argv.py "$ex" "$glo" "$ro"
432
433 ## STDOUT:
434 ['a b c', 'a b c', 'a b c']
435 ## END
436 ## BUG dash STDOUT:
437 ['a', 'a b c', 'a']
438 ## END
439
440
441 #### aliased assignment doesn't split
442 shopt -s expand_aliases || true
443 words='a b c'
444 alias e=export
445 alias r=readonly
446 e ex=$words
447 r ro=$words
448 argv.py "$ex" "$ro"
449 ## BUG dash STDOUT:
450 ['a', 'a']
451 ## END
452 ## STDOUT:
453 ['a b c', 'a b c']
454 ## END
455
456
457 #### assignment using dynamic keyword (splits in most shells, not in zsh/osh)
458 words='a b c'
459 e=export
460 r=readonly
461 $e ex=$words
462 $r ro=$words
463 argv.py "$ex" "$ro"
464
465 # zsh and OSH are smart
466 ## STDOUT:
467 ['a b c', 'a b c']
468 ## END
469
470 ## OK dash/bash/mksh STDOUT:
471 ['a', 'a']
472 ## END
473
474
475 #### assignment using dynamic var names doesn't split
476 words='a b c'
477 arg_ex=ex=$words
478 arg_ro=ro=$words
479
480 # no quotes, this is split of course
481 export $arg_ex
482 readonly $arg_ro
483
484 argv.py "$ex" "$ro"
485
486 arg_ex2=ex2=$words
487 arg_ro2=ro2=$words
488
489 # quotes, no splitting
490 export "$arg_ex2"
491 readonly "$arg_ro2"
492
493 argv.py "$ex2" "$ro2"
494
495 ## STDOUT:
496 ['a b c', 'a b c']
497 ['a b c', 'a b c']
498 ## END
499 ## OK dash/bash/mksh STDOUT:
500 ['a', 'a']
501 ['a b c', 'a b c']
502 ## END
503
504 #### assign and glob
505 cd $TMP
506 touch foo=a foo=b
507 foo=*
508 argv.py "$foo"
509 unset foo
510
511 export foo=*
512 argv.py "$foo"
513 unset foo
514
515 ## STDOUT:
516 ['*']
517 ['*']
518 ## END
519 ## BUG dash STDOUT:
520 ['*']
521 ['b']
522 ## END
523
524 #### declare and glob
525 cd $TMP
526 touch foo=a foo=b
527 typeset foo=*
528 argv.py "$foo"
529 unset foo
530 ## STDOUT:
531 ['*']
532 ## END
533 ## N-I dash STDOUT:
534 ['']
535 ## END
536
537 #### readonly $x where x='b c'
538 one=a
539 two='b c'
540 readonly $two $one
541 a=new
542 echo status=$?
543 b=new
544 echo status=$?
545 c=new
546 echo status=$?
547
548 # in OSH and zsh, this is an invalid variable name
549 ## status: 1
550 ## stdout-json: ""
551
552 # most shells make two variable read-only
553
554 ## OK dash/mksh status: 2
555 ## OK bash status: 0
556 ## OK bash STDOUT:
557 status=1
558 status=1
559 status=1
560 ## END
561
562 #### readonly a=(1 2) no_value c=(3 4) makes 'no_value' readonly
563 readonly a=(1 2) no_value c=(3 4)
564 no_value=x
565 ## status: 1
566 ## stdout-json: ""
567 ## OK dash status: 2
568
569 #### export a=1 no_value c=2
570 no_value=foo
571 export a=1 no_value c=2
572 printenv.py no_value
573 ## STDOUT:
574 foo
575 ## END
576
577 #### local a=loc $var c=loc
578 var='b'
579 b=global
580 echo $b
581 f() {
582 local a=loc $var c=loc
583 argv.py "$a" "$b" "$c"
584 }
585 f
586 ## STDOUT:
587 global
588 ['loc', '', 'loc']
589 ## END
590 ## BUG dash STDOUT:
591 global
592 ['loc', 'global', 'loc']
593 ## END
594
595 #### redirect after assignment builtin (what's going on with dash/bash/mksh here?)
596 readonly x=$(stdout_stderr.py) 2>/dev/null
597 echo done
598 ## STDOUT:
599 done
600 ## END
601 ## STDERR:
602 STDERR
603 ## END
604 ## BUG zsh stderr-json: ""
605
606 #### redirect after command sub (like case above but without assignment builtin)
607 echo stdout=$(stdout_stderr.py) 2>/dev/null
608 ## STDOUT:
609 stdout=STDOUT
610 ## END
611 ## STDERR:
612 STDERR
613 ## END
614
615 #### redirect after bare assignment
616 x=$(stdout_stderr.py) 2>/dev/null
617 echo done
618 ## STDOUT:
619 done
620 ## END
621 ## stderr-json: ""
622 ## BUG bash STDERR:
623 STDERR
624 ## END
625
626 #### redirect after declare -p
627 case $SH in *dash) exit 99 ;; esac # stderr unpredictable
628
629 foo=bar
630 typeset -p foo 1>&2
631
632 # zsh and mksh agree on exact output, which we don't really care about
633 ## STDERR:
634 typeset foo=bar
635 ## END
636 ## OK bash STDERR:
637 declare -- foo="bar"
638 ## END
639 ## OK osh STDERR:
640 declare -- foo=bar
641 ## END
642 ## N-I dash status: 99
643 ## N-I dash stderr-json: ""
644 ## stdout-json: ""
645
646 #### declare -a arr does not remove existing arrays (OSH regression)
647 case $SH in (dash) exit 99;; esac # dash does not support arrays
648
649 declare -a arr
650 arr=(foo bar baz)
651 declare -a arr
652 echo arr:${#arr[@]}
653 ## STDOUT:
654 arr:3
655 ## END
656 ## N-I dash status: 99
657 ## N-I dash stdout-json: ""
658
659 #### declare -A dict does not remove existing arrays (OSH regression)
660 case $SH in (dash|mksh) exit 99;; esac # dash/mksh does not support associative arrays
661
662 declare -A dict
663 dict['foo']=hello
664 dict['bar']=oil
665 dict['baz']=world
666 declare -A dict
667 echo dict:${#dict[@]}
668 ## STDOUT:
669 dict:3
670 ## END
671 ## N-I dash/mksh status: 99
672 ## N-I dash/mksh stdout-json: ""