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