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 ## status: 2
440 ## stdout-json: ""
441 ## OK mksh status: 1
442 ## BUG bash status: 0
443 ## BUG bash stdout: hello
444
445 #### <> for read/write
446 echo first >$TMP/rw.txt
447 exec 8<>$TMP/rw.txt
448 read line <&8
449 echo line=$line
450 echo second 1>&8
451 echo CONTENTS
452 cat $TMP/rw.txt
453 ## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
454
455 #### <> for read/write named pipes
456 rm -f "$TMP/f.pipe"
457 mkfifo "$TMP/f.pipe"
458 exec 8<> "$TMP/f.pipe"
459 echo first >&8
460 echo second >&8
461 read line1 <&8
462 read line2 <&8
463 exec 8<&-
464 echo line1=$line1 line2=$line2
465 ## stdout: line1=first line2=second
466
467 #### &>> appends stdout and stderr
468
469 # Fix for flaky tests: dash behaves non-deterministically under load! It
470 # doesn't implement the behavior anyway so I don't care why.
471 case $SH in
472 *dash)
473 exit 1
474 ;;
475 esac
476
477 echo "ok" > $TMP/f.txt
478 stdout_stderr.py &>> $TMP/f.txt
479 grep ok $TMP/f.txt >/dev/null && echo 'ok'
480 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
481 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
482 ## STDOUT:
483 ok
484 ok
485 ok
486 ## END
487 ## N-I dash stdout-json: ""
488 ## N-I dash status: 1
489
490 #### exec redirect then various builtins
491 exec 5>$TMP/log.txt
492 echo hi >&5
493 set -o >&5
494 echo done
495 ## STDOUT:
496 done
497 ## END
498
499 #### >$file touches a file
500 rm -f myfile
501 test -f myfile
502 echo status=$?
503 >myfile
504 test -f myfile
505 echo status=$?
506 ## STDOUT:
507 status=1
508 status=0
509 ## END
510 # regression for OSH
511 ## stderr-json: ""
512
513 #### $(< $file) yields the contents of the file
514
515 echo FOO > myfile
516 foo=$(< myfile)
517 echo $foo
518 ## STDOUT:
519 FOO
520 ## END
521 ## N-I dash/ash/yash stdout-json: "\n"
522
523 #### $(< file) with more statements
524
525 # note that it doesn't do this without a command sub!
526 # It's apparently a special case in bash, mksh, and zsh?
527 foo=$(echo begin; < myfile)
528 echo $foo
529 echo ---
530
531 foo=$(< myfile; echo end)
532 echo $foo
533 echo ---
534
535 foo=$(< myfile; <myfile)
536 echo $foo
537 echo ---
538
539 ## STDOUT:
540 begin
541 ---
542 end
543 ---
544
545 ---
546 ## END
547 # weird, zsh behaves differently
548 ## OK zsh STDOUT:
549 begin
550 FOO
551 ---
552 FOO
553 end
554 ---
555 FOO
556 FOO
557 ---
558 ## END
559
560
561 #### < file in pipeline and subshell doesn't work
562 echo FOO > file2
563
564 # This only happens in command subs, which is weird
565 < file2 | tr A-Z a-z
566 ( < file2 )
567 echo end
568 ## STDOUT:
569 end
570 ## END
571
572 #### 2>&1 with no command
573 ( exit 42 ) # status is reset after this
574 echo status=$?
575 2>&1
576 echo status=$?
577 ## STDOUT:
578 status=42
579 status=0
580 ## END
581 ## stderr-json: ""
582
583 #### 2&>1 (is it a redirect or is it like a&>1)
584 2&>1
585 echo status=$?
586 ## STDOUT:
587 status=127
588 ## END
589 ## OK mksh/dash STDOUT:
590 status=0
591 ## END
592
593 #### can't mention big file descriptor
594 echo hi 9>&1
595 # trivia: 23 is the max descriptor for mksh
596 #echo hi 24>&1
597 echo hi 99>&1
598 echo hi 100>&1
599 ## OK osh STDOUT:
600 hi
601 hi
602 hi 100
603 ## END
604 ## STDOUT:
605 hi
606 hi 99
607 hi 100
608 ## END
609 ## BUG bash STDOUT:
610 hi
611 hi
612 hi
613 ## END
614
615 #### : >/dev/null 2> / (OSH regression: fail to pop fd frame)
616 # oil 0.8.pre4 fails to restore fds after redirection failure. In the
617 # following case, the fd frame remains after the redirection failure
618 # "2> /" so that the effect of redirection ">/dev/null" remains after
619 # the completion of the command.
620 : >/dev/null 2> /
621 echo hello
622 ## stdout: hello
623 ## OK dash stdout-json: ""
624 ## OK dash status: 2
625 ## OK mksh stdout-json: ""
626 ## OK mksh status: 1
627 # dash/mksh terminates the execution of script on the redirection.
628
629 #### echo foo >&100 (OSH regression: does not fail with invalid fd 100)
630 # oil 0.8.pre4 does not fail with non-existent fd 100.
631 fd=100
632 echo foo >&$fd
633 ## stdout-json: ""
634 ## status: 1
635 ## OK dash status: 2
636
637 #### echo foo >&N where N is first unused fd
638 # 1. prepare default fd for internal uses
639 minfd=10
640 case ${SH##*/} in
641 (mksh) minfd=24 ;;
642 (osh) minfd=100 ;;
643 esac
644
645 # 2. prepare first unused fd
646 fd=$minfd
647 is-fd-open() { : >&$1; }
648 while is-fd-open "$fd"; do
649 : $((fd+=1))
650
651 # prevent infinite loop for broken oils-for-unix
652 if test $fd -gt 1000; then
653 break
654 fi
655 done
656
657 # 3. test
658 echo foo >&$fd
659 ## stdout-json: ""
660 ## status: 1
661 ## OK dash status: 2
662
663 #### exec {fd}>&- (OSH regression: fails to close fd)
664 # mksh, dash do not implement {fd} redirections.
665 case $SH in (mksh|dash) exit 1 ;; esac
666 # oil 0.8.pre4 fails to close fd by {fd}&-.
667 exec {fd}>file1
668 echo foo >&$fd
669 exec {fd}>&-
670 echo bar >&$fd
671 cat file1
672 ## stdout: foo
673 ## N-I mksh/dash stdout-json: ""
674 ## N-I mksh/dash status: 1