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