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=
305 x=global
306 ## END
307 ## BUG bash STDOUT:
308 x=temp-binding
309 x=mutated-temp
310 x=local
311 x=global
312 x=global
313 ## END
314 ## BUG mksh STDOUT:
315 x=temp-binding
316 x=mutated-temp
317 x=local
318 x=mutated-temp
319 x=mutated-temp
320 ## END
321 ## BUG yash STDOUT:
322 # yash has no locals
323 x=temp-binding
324 x=mutated-temp
325 x=mutated-temp
326 x=
327 x=
328 ## END
329
330 #### Test above without 'local' (which is not POSIX)
331 f() {
332 echo "x=$x"
333
334 x=mutated-temp # mutate temp frame
335 echo "x=$x"
336
337 # Unset it
338 unset x
339 echo "x=$x"
340 }
341
342 x=global
343 x=temp-binding f
344 echo "x=$x"
345
346 ## STDOUT:
347 x=temp-binding
348 x=mutated-temp
349 x=
350 x=global
351 ## END
352 ## BUG mksh/yash STDOUT:
353 x=temp-binding
354 x=mutated-temp
355 x=
356 x=
357 ## END
358 ## BUG bash STDOUT:
359 x=temp-binding
360 x=mutated-temp
361 x=global
362 x=global
363 ## END
364
365 #### Using ${x-default} after unsetting local shadowing a global
366 f() {
367 echo "x=$x"
368 local x='local'
369 echo "x=$x"
370 unset x
371 echo "- operator = ${x-default}"
372 echo ":- operator = ${x:-default}"
373 }
374 x=global
375 f
376 ## STDOUT:
377 x=global
378 x=local
379 - operator = default
380 :- operator = default
381 ## END
382 ## BUG mksh STDOUT:
383 x=global
384 x=local
385 - operator = global
386 :- operator = global
387 ## END
388
389 #### Using ${x-default} after unsetting a temp binding shadowing a global
390 f() {
391 echo "x=$x"
392 local x='local'
393 echo "x=$x"
394 unset x
395 echo "- operator = ${x-default}"
396 echo ":- operator = ${x:-default}"
397 }
398 x=global
399 x=temp-binding f
400 ## STDOUT:
401 x=temp-binding
402 x=local
403 - operator = default
404 :- operator = default
405 ## END
406 ## BUG mksh STDOUT:
407 x=temp-binding
408 x=local
409 - operator = temp-binding
410 :- operator = temp-binding
411 ## END
412 ## BUG bash STDOUT:
413 x=temp-binding
414 x=local
415 - operator = global
416 :- operator = global
417 ## END
418
419 #### static assignment doesn't split
420 words='a b c'
421 export ex=$words
422 glo=$words
423 readonly ro=$words
424 argv.py "$ex" "$glo" "$ro"
425
426 ## STDOUT:
427 ['a b c', 'a b c', 'a b c']
428 ## END
429 ## BUG dash STDOUT:
430 ['a', 'a b c', 'a']
431 ## END
432
433
434 #### aliased assignment doesn't split
435 shopt -s expand_aliases || true
436 words='a b c'
437 alias e=export
438 alias r=readonly
439 e ex=$words
440 r ro=$words
441 argv.py "$ex" "$ro"
442 ## BUG dash STDOUT:
443 ['a', 'a']
444 ## END
445 ## STDOUT:
446 ['a b c', 'a b c']
447 ## END
448
449
450 #### assignment using dynamic keyword (splits in most shells, not in zsh/osh)
451 words='a b c'
452 e=export
453 r=readonly
454 $e ex=$words
455 $r ro=$words
456 argv.py "$ex" "$ro"
457
458 # zsh and OSH are smart
459 ## STDOUT:
460 ['a b c', 'a b c']
461 ## END
462
463 ## OK dash/bash/mksh STDOUT:
464 ['a', 'a']
465 ## END
466
467
468 #### assignment using dynamic var names doesn't split
469 words='a b c'
470 arg_ex=ex=$words
471 arg_ro=ro=$words
472
473 # no quotes, this is split of course
474 export $arg_ex
475 readonly $arg_ro
476
477 argv.py "$ex" "$ro"
478
479 arg_ex2=ex2=$words
480 arg_ro2=ro2=$words
481
482 # quotes, no splitting
483 export "$arg_ex2"
484 readonly "$arg_ro2"
485
486 argv.py "$ex2" "$ro2"
487
488 ## STDOUT:
489 ['a b c', 'a b c']
490 ['a b c', 'a b c']
491 ## END
492 ## OK dash/bash/mksh STDOUT:
493 ['a', 'a']
494 ['a b c', 'a b c']
495 ## END
496
497 #### assign and glob
498 cd $TMP
499 touch foo=a foo=b
500 foo=*
501 argv.py "$foo"
502 unset foo
503
504 export foo=*
505 argv.py "$foo"
506 unset foo
507
508 ## STDOUT:
509 ['*']
510 ['*']
511 ## END
512 ## BUG dash STDOUT:
513 ['*']
514 ['b']
515 ## END
516
517 #### declare and glob
518 cd $TMP
519 touch foo=a foo=b
520 typeset foo=*
521 argv.py "$foo"
522 unset foo
523 ## STDOUT:
524 ['*']
525 ## END
526 ## N-I dash STDOUT:
527 ['']
528 ## END
529
530 #### readonly $x where x='b c'
531 one=a
532 two='b c'
533 readonly $two $one
534 a=new
535 echo status=$?
536 b=new
537 echo status=$?
538 c=new
539 echo status=$?
540
541 # in OSH and zsh, this is an invalid variable name
542 ## status: 1
543 ## stdout-json: ""
544
545 # most shells make two variable read-only
546
547 ## OK dash/mksh status: 2
548 ## OK bash status: 0
549 ## OK bash STDOUT:
550 status=1
551 status=1
552 status=1
553 ## END
554
555 #### readonly a=(1 2) no_value c=(3 4) makes 'no_value' readonly
556 readonly a=(1 2) no_value c=(3 4)
557 no_value=x
558 ## status: 1
559 ## stdout-json: ""
560 ## OK dash status: 2
561
562 #### export a=1 no_value c=2
563 no_value=foo
564 export a=1 no_value c=2
565 printenv.py no_value
566 ## STDOUT:
567 foo
568 ## END
569
570 #### local a=loc $var c=loc
571 var='b'
572 b=global
573 echo $b
574 f() {
575 local a=loc $var c=loc
576 argv.py "$a" "$b" "$c"
577 }
578 f
579 ## STDOUT:
580 global
581 ['loc', '', 'loc']
582 ## END
583 ## BUG dash STDOUT:
584 global
585 ['loc', 'global', 'loc']
586 ## END
587
588 #### redirect after assignment builtin (what's going on with dash/bash/mksh here?)
589 readonly x=$(stdout_stderr.py) 2>/dev/null
590 echo done
591 ## STDOUT:
592 done
593 ## END
594 ## STDERR:
595 STDERR
596 ## END
597 ## BUG zsh stderr-json: ""
598
599 #### redirect after command sub (like case above but without assignment builtin)
600 echo stdout=$(stdout_stderr.py) 2>/dev/null
601 ## STDOUT:
602 stdout=STDOUT
603 ## END
604 ## STDERR:
605 STDERR
606 ## END
607
608 #### redirect after bare assignment
609 x=$(stdout_stderr.py) 2>/dev/null
610 echo done
611 ## STDOUT:
612 done
613 ## END
614 ## stderr-json: ""
615 ## BUG bash STDERR:
616 STDERR
617 ## END
618
619 #### redirect after declare -p
620 case $SH in *dash) exit 99 ;; esac # stderr unpredictable
621
622 foo=bar
623 typeset -p foo 1>&2
624
625 # zsh and mksh agree on exact output, which we don't really care about
626 ## STDERR:
627 typeset foo=bar
628 ## END
629 ## OK bash STDERR:
630 declare -- foo="bar"
631 ## END
632 ## OK osh STDERR:
633 declare -- foo='bar'
634 ## END
635 ## N-I dash status: 99
636 ## N-I dash stderr-json: ""
637 ## stdout-json: ""
638