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 ## BUG dash status: 0
123 ## OK mksh/zsh status: 1
124 ## status: 2
125
126 #### Trying to run keyword 'for'
127 FOO=bar for
128 ## status: 127
129 ## OK zsh status: 1
130
131 #### Empty env binding
132 EMPTY= printenv.py EMPTY
133 ## stdout:
134
135 #### Assignment doesn't do word splitting
136 words='one two'
137 a=$words
138 argv.py "$a"
139 ## stdout: ['one two']
140
141 #### Assignment doesn't do glob expansion
142 touch _tmp/z.Z _tmp/zz.Z
143 a=_tmp/*.Z
144 argv.py "$a"
145 ## stdout: ['_tmp/*.Z']
146
147 #### Env binding in readonly/declare is NOT exported! (pitfall)
148
149 # All shells agree on this, but it's very confusing behavior.
150 FOO=foo readonly v=$(printenv.py FOO)
151 echo "v=$v"
152
153 # bash has probems here:
154 FOO=foo readonly v2=$FOO
155 echo "v2=$v2"
156
157 ## STDOUT:
158 v=None
159 v2=foo
160 ## END
161 ## BUG bash STDOUT:
162 v=None
163 v2=
164 ## END
165
166 #### assignments / array assignments not interpreted after 'echo'
167 a=1 echo b[0]=2 c=3
168 ## stdout: b[0]=2 c=3
169 # zsh interprets [0] as some kind of glob
170 ## OK zsh stdout-json: ""
171 ## OK zsh status: 1
172
173 #### dynamic local variables (and splitting)
174 f() {
175 local "$1" # Only x is assigned here
176 echo x=\'$x\'
177 echo a=\'$a\'
178
179 local $1 # x and a are assigned here
180 echo x=\'$x\'
181 echo a=\'$a\'
182 }
183 f 'x=y a=b'
184 ## OK dash/bash/mksh STDOUT:
185 x='y a=b'
186 a=''
187 x='y'
188 a='b'
189 ## END
190 # osh and zsh don't do word splitting
191 ## STDOUT:
192 x='y a=b'
193 a=''
194 x='y a=b'
195 a=''
196 ## END
197
198 #### readonly x= gives empty string (regression)
199 readonly x=
200 argv.py "$x"
201 ## STDOUT:
202 ['']
203 ## END
204
205 #### 'local x' does not set variable
206 set -o nounset
207 f() {
208 local x
209 echo $x
210 }
211 f
212 ## status: 1
213 ## OK dash status: 2
214 ## BUG zsh status: 0
215
216 #### 'local -a x' does not set variable
217 set -o nounset
218 f() {
219 local -a x
220 echo $x
221 }
222 f
223 ## status: 1
224 ## OK dash status: 2
225 ## BUG zsh status: 0
226
227 #### 'local x' and then array assignment
228 f() {
229 local x
230 x[3]=foo
231 echo ${x[3]}
232 }
233 f
234 ## status: 0
235 ## stdout: foo
236 ## N-I dash status: 2
237 ## N-I dash stdout-json: ""
238 ## BUG zsh stdout: o
239
240 #### 'declare -A' and then dict assignment
241 declare -A foo
242 key=bar
243 foo["$key"]=value
244 echo ${foo["bar"]}
245 ## status: 0
246 ## stdout: value
247 ## N-I dash status: 2
248 ## N-I dash stdout-json: ""
249 ## N-I mksh status: 1
250 ## N-I mksh stdout-json: ""
251
252 #### declare in an if statement
253 # bug caught by my feature detection snippet in bash-completion
254 if ! foo=bar; then
255 echo BAD
256 fi
257 echo $foo
258 if ! eval 'spam=eggs'; then
259 echo BAD
260 fi
261 echo $spam
262 ## STDOUT:
263 bar
264 eggs
265 ## END
266
267
268 #### Modify a temporary binding
269 # (regression for bug found by Michael Greenberg)
270 f() {
271 echo "x before = $x"
272 x=$((x+1))
273 echo "x after = $x"
274 }
275 x=5 f
276 ## STDOUT:
277 x before = 5
278 x after = 6
279 ## END
280
281 #### Reveal existence of "temp frame" (All shells disagree here!!!)
282 f() {
283 echo "x=$x"
284
285 x=mutated-temp # mutate temp frame
286 echo "x=$x"
287
288 # Declare a new local
289 local x='local'
290 echo "x=$x"
291
292 # Unset it
293 unset x
294 echo "x=$x"
295 }
296
297 x=global
298 x=temp-binding f
299 echo "x=$x"
300
301 ## STDOUT:
302 x=temp-binding
303 x=mutated-temp
304 x=local
305 x=
306 x=global
307 ## END
308 ## BUG dash STDOUT:
309 x=temp-binding
310 x=mutated-temp
311 x=local
312 x=
313 x=mutated-temp
314 ## END
315 ## BUG bash STDOUT:
316 x=temp-binding
317 x=mutated-temp
318 x=local
319 x=global
320 x=global
321 ## END
322 ## BUG mksh STDOUT:
323 x=temp-binding
324 x=mutated-temp
325 x=local
326 x=mutated-temp
327 x=mutated-temp
328 ## END
329 ## BUG yash STDOUT:
330 # yash has no locals
331 x=temp-binding
332 x=mutated-temp
333 x=mutated-temp
334 x=
335 x=
336 ## END
337
338 #### Test above without 'local' (which is not POSIX)
339 f() {
340 echo "x=$x"
341
342 x=mutated-temp # mutate temp frame
343 echo "x=$x"
344
345 # Unset it
346 unset x
347 echo "x=$x"
348 }
349
350 x=global
351 x=temp-binding f
352 echo "x=$x"
353
354 ## STDOUT:
355 x=temp-binding
356 x=mutated-temp
357 x=
358 x=global
359 ## END
360 ## BUG dash/mksh/yash STDOUT:
361 x=temp-binding
362 x=mutated-temp
363 x=
364 x=
365 ## END
366 ## BUG bash STDOUT:
367 x=temp-binding
368 x=mutated-temp
369 x=global
370 x=global
371 ## END
372
373 #### Using ${x-default} after unsetting local shadowing a global
374 f() {
375 echo "x=$x"
376 local x='local'
377 echo "x=$x"
378 unset x
379 echo "- operator = ${x-default}"
380 echo ":- operator = ${x:-default}"
381 }
382 x=global
383 f
384 ## STDOUT:
385 x=global
386 x=local
387 - operator = default
388 :- operator = default
389 ## END
390 ## BUG mksh STDOUT:
391 x=global
392 x=local
393 - operator = global
394 :- operator = global
395 ## END
396
397 #### Using ${x-default} after unsetting a temp binding shadowing a global
398 f() {
399 echo "x=$x"
400 local x='local'
401 echo "x=$x"
402 unset x
403 echo "- operator = ${x-default}"
404 echo ":- operator = ${x:-default}"
405 }
406 x=global
407 x=temp-binding f
408 ## STDOUT:
409 x=temp-binding
410 x=local
411 - operator = default
412 :- operator = default
413 ## END
414 ## BUG mksh STDOUT:
415 x=temp-binding
416 x=local
417 - operator = temp-binding
418 :- operator = temp-binding
419 ## END
420 ## BUG bash STDOUT:
421 x=temp-binding
422 x=local
423 - operator = global
424 :- operator = global
425 ## END
426
427 #### static assignment doesn't split
428 words='a b c'
429 export ex=$words
430 glo=$words
431 readonly ro=$words
432 argv.py "$ex" "$glo" "$ro"
433
434 ## STDOUT:
435 ['a b c', 'a b c', 'a b c']
436 ## END
437 ## BUG dash STDOUT:
438 ['a', 'a b c', 'a']
439 ## END
440
441
442 #### aliased assignment doesn't split
443 shopt -s expand_aliases || true
444 words='a b c'
445 alias e=export
446 alias r=readonly
447 e ex=$words
448 r ro=$words
449 argv.py "$ex" "$ro"
450 ## BUG dash STDOUT:
451 ['a', 'a']
452 ## END
453 ## STDOUT:
454 ['a b c', 'a b c']
455 ## END
456
457
458 #### assignment using dynamic keyword (splits in most shells, not in zsh/osh)
459 words='a b c'
460 e=export
461 r=readonly
462 $e ex=$words
463 $r ro=$words
464 argv.py "$ex" "$ro"
465
466 # zsh and OSH are smart
467 ## STDOUT:
468 ['a b c', 'a b c']
469 ## END
470
471 ## OK dash/bash/mksh STDOUT:
472 ['a', 'a']
473 ## END
474
475
476 #### assignment using dynamic var names doesn't split
477 words='a b c'
478 arg_ex=ex=$words
479 arg_ro=ro=$words
480
481 # no quotes, this is split of course
482 export $arg_ex
483 readonly $arg_ro
484
485 argv.py "$ex" "$ro"
486
487 arg_ex2=ex2=$words
488 arg_ro2=ro2=$words
489
490 # quotes, no splitting
491 export "$arg_ex2"
492 readonly "$arg_ro2"
493
494 argv.py "$ex2" "$ro2"
495
496 ## STDOUT:
497 ['a b c', 'a b c']
498 ['a b c', 'a b c']
499 ## END
500 ## OK dash/bash/mksh STDOUT:
501 ['a', 'a']
502 ['a b c', 'a b c']
503 ## END
504
505 #### assign and glob
506 cd $TMP
507 touch foo=a foo=b
508 foo=*
509 argv.py "$foo"
510 unset foo
511
512 export foo=*
513 argv.py "$foo"
514 unset foo
515
516 ## STDOUT:
517 ['*']
518 ['*']
519 ## END
520 ## BUG dash STDOUT:
521 ['*']
522 ['b']
523 ## END
524
525 #### declare and glob
526 cd $TMP
527 touch foo=a foo=b
528 typeset foo=*
529 argv.py "$foo"
530 unset foo
531 ## STDOUT:
532 ['*']
533 ## END
534 ## N-I dash STDOUT:
535 ['']
536 ## END
537
538 #### readonly $x where x='b c'
539 one=a
540 two='b c'
541 readonly $two $one
542 a=new
543 echo status=$?
544 b=new
545 echo status=$?
546 c=new
547 echo status=$?
548
549 # in OSH and zsh, this is an invalid variable name
550 ## status: 1
551 ## stdout-json: ""
552
553 # most shells make two variable read-only
554
555 ## OK dash/mksh status: 2
556 ## OK bash status: 0
557 ## OK bash STDOUT:
558 status=1
559 status=1
560 status=1
561 ## END
562
563 #### readonly a=(1 2) no_value c=(3 4) makes 'no_value' readonly
564 readonly a=(1 2) no_value c=(3 4)
565 no_value=x
566 ## status: 1
567 ## stdout-json: ""
568 ## OK dash status: 2
569
570 #### export a=1 no_value c=2
571 no_value=foo
572 export a=1 no_value c=2
573 printenv.py no_value
574 ## STDOUT:
575 foo
576 ## END
577
578 #### local a=loc $var c=loc
579 var='b'
580 b=global
581 echo $b
582 f() {
583 local a=loc $var c=loc
584 argv.py "$a" "$b" "$c"
585 }
586 f
587 ## STDOUT:
588 global
589 ['loc', '', 'loc']
590 ## END
591 ## BUG dash STDOUT:
592 global
593 ['loc', 'global', 'loc']
594 ## END
595
596 #### redirect after assignment builtin (what's going on with dash/bash/mksh here?)
597 readonly x=$(stdout_stderr.py) 2>/dev/null
598 echo done
599 ## STDOUT:
600 done
601 ## END
602 ## STDERR:
603 STDERR
604 ## END
605 ## BUG zsh stderr-json: ""
606
607 #### redirect after command sub (like case above but without assignment builtin)
608 echo stdout=$(stdout_stderr.py) 2>/dev/null
609 ## STDOUT:
610 stdout=STDOUT
611 ## END
612 ## STDERR:
613 STDERR
614 ## END
615
616 #### redirect after bare assignment
617 x=$(stdout_stderr.py) 2>/dev/null
618 echo done
619 ## STDOUT:
620 done
621 ## END
622 ## stderr-json: ""
623 ## BUG bash STDERR:
624 STDERR
625 ## END
626
627 #### redirect after declare -p
628 case $SH in *dash) exit 99 ;; esac # stderr unpredictable
629
630 foo=bar
631 typeset -p foo 1>&2
632 ## STDERR:
633 typeset foo=bar
634 ## END
635 ## OK bash STDERR:
636 declare -- foo="bar"
637 ## END
638 ## OK osh STDERR:
639 foo
640 ## END
641 ## N-I dash status: 99
642 ## N-I dash stderr-json: ""
643 ## stdout-json: ""
644