1 |
#!/usr/bin/env bash |
2 |
|
3 |
#### >& |
4 |
echo hi 1>&2 |
5 |
## stderr: hi |
6 |
|
7 |
#### <& |
8 |
# Is there a simpler test case for this? |
9 |
echo foo > $TMP/lessamp.txt |
10 |
exec 6< $TMP/lessamp.txt |
11 |
read line <&6 |
12 |
echo "[$line]" |
13 |
## stdout: [foo] |
14 |
|
15 |
#### Leading redirect |
16 |
echo hello >$TMP/hello.txt # temporary fix |
17 |
<$TMP/hello.txt cat |
18 |
## stdout: hello |
19 |
|
20 |
#### Nonexistent file |
21 |
cat <$TMP/nonexistent.txt |
22 |
echo status=$? |
23 |
## stdout: status=1 |
24 |
## OK dash stdout: status=2 |
25 |
|
26 |
#### Redirect in command sub |
27 |
FOO=$(echo foo 1>&2) |
28 |
echo $FOO |
29 |
## stdout: |
30 |
## stderr: foo |
31 |
|
32 |
#### Redirect in assignment |
33 |
# dash captures stderr to a file here, which seems correct. Bash doesn't and |
34 |
# just lets it go to actual stderr. |
35 |
# For now we agree with dash/mksh, since it involves fewer special cases in the |
36 |
# code. |
37 |
|
38 |
FOO=$(echo foo 1>&2) 2>$TMP/no-command.txt |
39 |
echo FILE= |
40 |
cat $TMP/no-command.txt |
41 |
echo "FOO=$FOO" |
42 |
## STDOUT: |
43 |
FILE= |
44 |
foo |
45 |
FOO= |
46 |
## END |
47 |
## BUG bash STDOUT: |
48 |
FILE= |
49 |
FOO= |
50 |
## END |
51 |
|
52 |
#### Redirect in function body. |
53 |
fun() { echo hi; } 1>&2 |
54 |
fun |
55 |
## stdout-json: "" |
56 |
## stderr-json: "hi\n" |
57 |
|
58 |
#### Bad redirects in function body |
59 |
empty='' |
60 |
fun() { echo hi; } > $empty |
61 |
fun |
62 |
echo status=$? |
63 |
## stdout: status=1 |
64 |
## OK dash stdout: status=2 |
65 |
|
66 |
#### Redirect in function body is evaluated multiple times |
67 |
i=0 |
68 |
fun() { echo "file $i"; } 1> "$TMP/file$((i++))" |
69 |
fun |
70 |
fun |
71 |
echo i=$i |
72 |
echo __ |
73 |
cat $TMP/file0 |
74 |
echo __ |
75 |
cat $TMP/file1 |
76 |
## STDOUT: |
77 |
i=2 |
78 |
__ |
79 |
file 1 |
80 |
__ |
81 |
file 2 |
82 |
## END |
83 |
## N-I dash stdout-json: "" |
84 |
## N-I dash status: 2 |
85 |
|
86 |
#### Redirect in function body AND function call |
87 |
fun() { echo hi; } 1>&2 |
88 |
fun 2>&1 |
89 |
## stdout-json: "hi\n" |
90 |
## stderr-json: "" |
91 |
|
92 |
#### Descriptor redirect with spaces |
93 |
# Hm this seems like a failure of lookahead! The second thing should look to a |
94 |
# file-like thing. |
95 |
# I think this is a posix issue. |
96 |
# tag: posix-issue |
97 |
echo one 1>&2 |
98 |
echo two 1 >&2 |
99 |
echo three 1>& 2 |
100 |
## stderr-json: "one\ntwo 1\nthree\n" |
101 |
|
102 |
#### Filename redirect with spaces |
103 |
# This time 1 *is* a descriptor, not a word. If you add a space between 1 and |
104 |
# >, it doesn't work. |
105 |
echo two 1> $TMP/file-redir1.txt |
106 |
cat $TMP/file-redir1.txt |
107 |
## stdout: two |
108 |
|
109 |
#### Quoted filename redirect with spaces |
110 |
# POSIX makes node of this |
111 |
echo two \1 > $TMP/file-redir2.txt |
112 |
cat $TMP/file-redir2.txt |
113 |
## stdout: two 1 |
114 |
|
115 |
#### Descriptor redirect with filename |
116 |
# bash/mksh treat this like a filename, not a descriptor. |
117 |
# dash aborts. |
118 |
echo one 1>&$TMP/nonexistent-filename__ |
119 |
echo "status=$?" |
120 |
## stdout: status=1 |
121 |
## BUG bash stdout: status=0 |
122 |
## OK dash stdout-json: "" |
123 |
## OK dash status: 2 |
124 |
|
125 |
#### redirect for loop |
126 |
for i in $(seq 3) |
127 |
do |
128 |
echo $i |
129 |
done > $TMP/redirect-for-loop.txt |
130 |
cat $TMP/redirect-for-loop.txt |
131 |
## stdout-json: "1\n2\n3\n" |
132 |
|
133 |
#### redirect subshell |
134 |
( echo foo ) 1>&2 |
135 |
## stderr: foo |
136 |
## stdout-json: "" |
137 |
|
138 |
#### Prefix redirect for loop -- not allowed |
139 |
>$TMP/redirect2.txt for i in $(seq 3) |
140 |
do |
141 |
echo $i |
142 |
done |
143 |
cat $TMP/redirect2.txt |
144 |
## status: 2 |
145 |
## OK mksh status: 1 |
146 |
|
147 |
#### Brace group redirect |
148 |
# Suffix works, but prefix does NOT work. |
149 |
# That comes from '| compound_command redirect_list' in the grammar! |
150 |
{ echo block-redirect; } > $TMP/br.txt |
151 |
cat $TMP/br.txt | wc -c |
152 |
## stdout: 15 |
153 |
|
154 |
#### Redirect echo to stderr, and then redirect all of stdout somewhere. |
155 |
{ echo foo 1>&2; echo 012345789; } > $TMP/block-stdout.txt |
156 |
cat $TMP/block-stdout.txt | wc -c |
157 |
## stderr: foo |
158 |
## stdout: 10 |
159 |
|
160 |
#### Redirect in the middle of two assignments |
161 |
FOO=foo >$TMP/out.txt BAR=bar printenv.py FOO BAR |
162 |
tac $TMP/out.txt |
163 |
## stdout-json: "bar\nfoo\n" |
164 |
|
165 |
#### Redirect in the middle of a command |
166 |
f=$TMP/out |
167 |
echo -n 1 2 '3 ' > $f |
168 |
echo -n 4 5 >> $f '6 ' |
169 |
echo -n 7 >> $f 8 '9 ' |
170 |
echo -n >> $f 1 2 '3 ' |
171 |
echo >> $f -n 4 5 '6 ' |
172 |
cat $f |
173 |
## stdout-json: "1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 " |
174 |
|
175 |
#### Named file descriptor |
176 |
exec {myfd}> $TMP/named-fd.txt |
177 |
echo named-fd-contents >& $myfd |
178 |
cat $TMP/named-fd.txt |
179 |
## stdout: named-fd-contents |
180 |
## status: 0 |
181 |
## N-I dash/mksh stdout-json: "" |
182 |
## N-I dash/mksh status: 127 |
183 |
|
184 |
#### Double digit fd (20> file) |
185 |
exec 20> "$TMP/double-digit-fd.txt" |
186 |
echo hello20 >&20 |
187 |
cat "$TMP/double-digit-fd.txt" |
188 |
## stdout: hello20 |
189 |
## BUG dash stdout-json: "" |
190 |
## BUG dash status: 127 |
191 |
|
192 |
#### : 9> fdleak (OSH regression) |
193 |
true 9> "$TMP/fd.txt" |
194 |
( echo world >&9 ) |
195 |
cat "$TMP/fd.txt" |
196 |
## stdout-json: "" |
197 |
|
198 |
#### : 3>&3 (OSH regression) |
199 |
: 3>&3 |
200 |
echo hello |
201 |
## stdout: hello |
202 |
## BUG mksh stdout-json: "" |
203 |
## BUG mksh status: 1 |
204 |
|
205 |
#### : 3>&3- |
206 |
: 3>&3- |
207 |
echo hello |
208 |
## stdout: hello |
209 |
## N-I dash/mksh stdout-json: "" |
210 |
## N-I mksh status: 1 |
211 |
## N-I dash status: 2 |
212 |
|
213 |
#### 3>&- << EOF (OSH regression: fail to restore fds) |
214 |
exec 3> "$TMP/fd.txt" |
215 |
echo hello 3>&- << EOF |
216 |
EOF |
217 |
echo world >&3 |
218 |
exec 3>&- # close |
219 |
cat "$TMP/fd.txt" |
220 |
## STDOUT: |
221 |
hello |
222 |
world |
223 |
## END |
224 |
|
225 |
#### Open file on descriptor 3 and write to it many times |
226 |
|
227 |
# different than case below because 3 is the likely first FD of open() |
228 |
|
229 |
exec 3> "$TMP/fd3.txt" |
230 |
echo hello >&3 |
231 |
echo world >&3 |
232 |
exec 3>&- # close |
233 |
cat "$TMP/fd3.txt" |
234 |
## STDOUT: |
235 |
hello |
236 |
world |
237 |
## END |
238 |
|
239 |
#### Open file on descriptor 4 and write to it many times |
240 |
|
241 |
# different than the case above because because 4 isn't the likely first FD |
242 |
|
243 |
exec 4> "$TMP/fd4.txt" |
244 |
echo hello >&4 |
245 |
echo world >&4 |
246 |
exec 4>&- # close |
247 |
cat "$TMP/fd4.txt" |
248 |
## STDOUT: |
249 |
hello |
250 |
world |
251 |
## END |
252 |
|
253 |
#### Redirect function stdout |
254 |
f() { echo one; echo two; } |
255 |
f > $TMP/redirect-func.txt |
256 |
cat $TMP/redirect-func.txt |
257 |
## stdout-json: "one\ntwo\n" |
258 |
|
259 |
#### Nested function stdout redirect |
260 |
# Shows that a stack is necessary. |
261 |
inner() { |
262 |
echo i1 |
263 |
echo i2 |
264 |
} |
265 |
outer() { |
266 |
echo o1 |
267 |
inner > $TMP/inner.txt |
268 |
echo o2 |
269 |
} |
270 |
outer > $TMP/outer.txt |
271 |
cat $TMP/inner.txt |
272 |
echo -- |
273 |
cat $TMP/outer.txt |
274 |
## stdout-json: "i1\ni2\n--\no1\no2\n" |
275 |
|
276 |
#### Redirect to empty string |
277 |
f='' |
278 |
echo s > "$f" |
279 |
echo "result=$?" |
280 |
set -o errexit |
281 |
echo s > "$f" |
282 |
echo DONE |
283 |
## stdout: result=1 |
284 |
## status: 1 |
285 |
## OK dash stdout: result=2 |
286 |
## OK dash status: 2 |
287 |
|
288 |
#### Redirect to file descriptor that's not open |
289 |
# Notes: |
290 |
# - dash doesn't allow file descriptors greater than 9. (This is a good |
291 |
# thing, because the bash chapter in AOSA book mentions that juggling user |
292 |
# vs. system file descriptors is a huge pain.) |
293 |
# - But somehow running in parallel under spec-runner.sh changes whether |
294 |
# descriptor 3 is open. e.g. 'echo hi 1>&3'. Possibly because of |
295 |
# /usr/bin/time. The _tmp/spec/*.task.txt file gets corrupted! |
296 |
# - Oh this is because I use time --output-file. That opens descriptor 3. And |
297 |
# then time forks the shell script. The file descriptor table is inherited. |
298 |
# - You actually have to set the file descriptor to something. What do |
299 |
# configure and debootstrap too? |
300 |
|
301 |
# 3/2020 note: file descriptor 9 failed on Travis, so I changed it to 8. The |
302 |
# process state isn't necessarly clean. TODO: Close the descriptor when OSH |
303 |
# supports it? |
304 |
|
305 |
echo hi 1>&8 |
306 |
## status: 1 |
307 |
## OK dash status: 2 |
308 |
|
309 |
#### Open descriptor with exec |
310 |
# What is the point of this? ./configure scripts and debootstrap use it. |
311 |
exec 3>&1 |
312 |
echo hi 1>&3 |
313 |
## stdout: hi |
314 |
## status: 0 |
315 |
|
316 |
#### Open multiple descriptors with exec |
317 |
# What is the point of this? ./configure scripts and debootstrap use it. |
318 |
exec 3>&1 |
319 |
exec 4>&1 |
320 |
echo three 1>&3 |
321 |
echo four 1>&4 |
322 |
## stdout-json: "three\nfour\n" |
323 |
## status: 0 |
324 |
|
325 |
#### >| to clobber |
326 |
echo XX >| $TMP/c.txt |
327 |
set -o noclobber |
328 |
echo YY > $TMP/c.txt # not globber |
329 |
echo status=$? |
330 |
cat $TMP/c.txt |
331 |
echo ZZ >| $TMP/c.txt |
332 |
cat $TMP/c.txt |
333 |
## stdout-json: "status=1\nXX\nZZ\n" |
334 |
## OK dash stdout-json: "status=2\nXX\nZZ\n" |
335 |
|
336 |
#### &> redirects stdout and stderr |
337 |
stdout_stderr.py &> $TMP/f.txt |
338 |
# order is indeterminate |
339 |
grep STDOUT $TMP/f.txt >/dev/null && echo 'ok' |
340 |
grep STDERR $TMP/f.txt >/dev/null && echo 'ok' |
341 |
## STDOUT: |
342 |
ok |
343 |
ok |
344 |
## END |
345 |
## N-I dash stdout: STDOUT |
346 |
## N-I dash stderr: STDERR |
347 |
## N-I dash status: 1 |
348 |
|
349 |
#### 1>&- to close file descriptor |
350 |
exec 5> "$TMP/f.txt" |
351 |
echo hello >&5 |
352 |
exec 5>&- |
353 |
echo world >&5 |
354 |
cat "$TMP/f.txt" |
355 |
## stdout-json: "hello\n" |
356 |
|
357 |
#### 1>&2- to move file descriptor |
358 |
exec 5> "$TMP/f.txt" |
359 |
echo hello5 >&5 |
360 |
exec 6>&5- |
361 |
echo world5 >&5 |
362 |
echo world6 >&6 |
363 |
exec 6>&- |
364 |
cat "$TMP/f.txt" |
365 |
## stdout-json: "hello5\nworld6\n" |
366 |
## N-I dash status: 2 |
367 |
## N-I dash stdout-json: "" |
368 |
## N-I mksh status: 1 |
369 |
## N-I mksh stdout-json: "" |
370 |
|
371 |
#### 1>&2- (Bash bug: fail to restore closed fd) |
372 |
exec 7> "$TMP/f.txt" |
373 |
: 8>&7 7>&- |
374 |
echo hello >&7 |
375 |
: 8>&7- |
376 |
echo world >&7 |
377 |
exec 7>&- |
378 |
cat "$TMP/f.txt" |
379 |
## status: 2 |
380 |
## stdout-json: "" |
381 |
## OK mksh status: 1 |
382 |
## BUG bash status: 0 |
383 |
## BUG bash stdout: hello |
384 |
|
385 |
#### <> for read/write |
386 |
echo first >$TMP/rw.txt |
387 |
exec 8<>$TMP/rw.txt |
388 |
read line <&8 |
389 |
echo line=$line |
390 |
echo second 1>&8 |
391 |
echo CONTENTS |
392 |
cat $TMP/rw.txt |
393 |
## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n" |
394 |
|
395 |
#### <> for read/write named pipes |
396 |
rm -f "$TMP/f.pipe" |
397 |
mkfifo "$TMP/f.pipe" |
398 |
exec 8<> "$TMP/f.pipe" |
399 |
echo first >&8 |
400 |
echo second >&8 |
401 |
read line1 <&8 |
402 |
read line2 <&8 |
403 |
exec 8<&- |
404 |
echo line1=$line1 line2=$line2 |
405 |
## stdout: line1=first line2=second |
406 |
|
407 |
#### &>> appends stdout and stderr |
408 |
|
409 |
# Fix for flaky tests: dash behaves non-deterministically under load! It |
410 |
# doesn't implement the behavior anyway so I don't care why. |
411 |
case $SH in |
412 |
*dash) |
413 |
exit 1 |
414 |
;; |
415 |
esac |
416 |
|
417 |
echo "ok" > $TMP/f.txt |
418 |
stdout_stderr.py &>> $TMP/f.txt |
419 |
grep ok $TMP/f.txt >/dev/null && echo 'ok' |
420 |
grep STDOUT $TMP/f.txt >/dev/null && echo 'ok' |
421 |
grep STDERR $TMP/f.txt >/dev/null && echo 'ok' |
422 |
## STDOUT: |
423 |
ok |
424 |
ok |
425 |
ok |
426 |
## END |
427 |
## N-I dash stdout-json: "" |
428 |
## N-I dash status: 1 |
429 |
|
430 |
#### exec redirect then various builtins |
431 |
exec 5>$TMP/log.txt |
432 |
echo hi >&5 |
433 |
set -o >&5 |
434 |
echo done |
435 |
## STDOUT: |
436 |
done |
437 |
## END |
438 |
|
439 |
#### >$file touches a file |
440 |
rm -f myfile |
441 |
test -f myfile |
442 |
echo status=$? |
443 |
>myfile |
444 |
test -f myfile |
445 |
echo status=$? |
446 |
## STDOUT: |
447 |
status=1 |
448 |
status=0 |
449 |
## END |
450 |
# regression for OSH |
451 |
## stderr-json: "" |
452 |
|
453 |
#### $(< $file) yields the contents of the file |
454 |
|
455 |
echo FOO > myfile |
456 |
foo=$(< myfile) |
457 |
echo $foo |
458 |
## STDOUT: |
459 |
FOO |
460 |
## END |
461 |
## N-I dash/ash/yash stdout-json: "\n" |
462 |
|
463 |
#### $(< file) with more statements |
464 |
|
465 |
# note that it doesn't do this without a command sub! |
466 |
# It's apparently a special case in bash, mksh, and zsh? |
467 |
foo=$(echo begin; < myfile) |
468 |
echo $foo |
469 |
echo --- |
470 |
|
471 |
foo=$(< myfile; echo end) |
472 |
echo $foo |
473 |
echo --- |
474 |
|
475 |
foo=$(< myfile; <myfile) |
476 |
echo $foo |
477 |
echo --- |
478 |
|
479 |
## STDOUT: |
480 |
begin |
481 |
--- |
482 |
end |
483 |
--- |
484 |
|
485 |
--- |
486 |
## END |
487 |
# weird, zsh behaves differently |
488 |
## OK zsh STDOUT: |
489 |
begin |
490 |
FOO |
491 |
--- |
492 |
FOO |
493 |
end |
494 |
--- |
495 |
FOO |
496 |
FOO |
497 |
--- |
498 |
## END |
499 |
|
500 |
|
501 |
#### < file in pipeline and subshell doesn't work |
502 |
echo FOO > file2 |
503 |
|
504 |
# This only happens in command subs, which is weird |
505 |
< file2 | tr A-Z a-z |
506 |
( < file2 ) |
507 |
echo end |
508 |
## STDOUT: |
509 |
end |
510 |
## END |
511 |
|
512 |
#### 2>&1 with no command |
513 |
( exit 42 ) # status is reset after this |
514 |
echo status=$? |
515 |
2>&1 |
516 |
echo status=$? |
517 |
## STDOUT: |
518 |
status=42 |
519 |
status=0 |
520 |
## END |
521 |
## stderr-json: "" |
522 |
|
523 |
#### 2&>1 (is it a redirect or is it like a&>1) |
524 |
2&>1 |
525 |
echo status=$? |
526 |
## STDOUT: |
527 |
status=127 |
528 |
## END |
529 |
## OK mksh/dash STDOUT: |
530 |
status=0 |
531 |
## END |
532 |
|
533 |
#### can't mention big file descriptor |
534 |
echo hi 9>&1 |
535 |
# 23 is the max descriptor fo rmksh |
536 |
#echo hi 24>&1 |
537 |
echo hi 99>&1 |
538 |
echo hi 100>&1 |
539 |
## OK osh STDOUT: |
540 |
hi |
541 |
hi |
542 |
hi 100 |
543 |
## END |
544 |
## STDOUT: |
545 |
hi |
546 |
hi 99 |
547 |
hi 100 |
548 |
## END |
549 |
## BUG bash STDOUT: |
550 |
hi |
551 |
hi |
552 |
hi |
553 |
## END |
554 |
|
555 |
#### : >/dev/null 2> / (OSH regression: fail to pop fd frame) |
556 |
# oil 0.8.pre4 fails to restore fds after redirection failure. In the |
557 |
# following case, the fd frame remains after the redirection failure |
558 |
# "2> /" so that the effect of redirection ">/dev/null" remains after |
559 |
# the completion of the command. |
560 |
: >/dev/null 2> / |
561 |
echo hello |
562 |
## stdout: hello |
563 |
## OK dash stdout-json: "" |
564 |
## OK dash status: 2 |
565 |
## OK mksh stdout-json: "" |
566 |
## OK mksh status: 1 |
567 |
# dash/mksh terminates the execution of script on the redirection. |
568 |
|
569 |
#### echo foo >&100 (OSH regression: does not fail with invalid fd 100) |
570 |
# oil 0.8.pre4 does not fail with non-existent fd 100. |
571 |
fd=100 |
572 |
echo foo >&$fd |
573 |
## stdout-json: "" |
574 |
## status: 1 |
575 |
## OK dash status: 2 |
576 |
|
577 |
#### echo foo >&N where N is first unused fd |
578 |
# 1. prepare default fd for internal uses |
579 |
minfd=10 |
580 |
case ${SH##*/} in |
581 |
(mksh) minfd=24 ;; |
582 |
(osh) minfd=100 ;; |
583 |
esac |
584 |
|
585 |
# 2. prepare first unused fd |
586 |
fd=$minfd |
587 |
is-fd-open() { : >&$1; } |
588 |
while is-fd-open "$fd"; do |
589 |
: $((fd+=1)) |
590 |
|
591 |
# prevent infinite loop for broken osh_eval |
592 |
if test $fd -gt 1000; then |
593 |
break |
594 |
fi |
595 |
done |
596 |
|
597 |
# 3. test |
598 |
echo foo >&$fd |
599 |
## stdout-json: "" |
600 |
## status: 1 |
601 |
## OK dash status: 2 |
602 |
|
603 |
#### exec {fd}>&- (OSH regression: fails to close fd) |
604 |
# mksh, dash do not implement {fd} redirections. |
605 |
case $SH in (mksh|dash) exit 1 ;; esac |
606 |
# oil 0.8.pre4 fails to close fd by {fd}&-. |
607 |
exec {fd}>file1 |
608 |
echo foo >&$fd |
609 |
exec {fd}>&- |
610 |
echo bar >&$fd |
611 |
cat file1 |
612 |
## stdout: foo |
613 |
## N-I mksh/dash stdout-json: "" |
614 |
## N-I mksh/dash status: 1 |