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 |
|