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 stdout_stderr.py &> $TMP/f.txt
360 # order is indeterminate
361 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
362 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
363 ## STDOUT:
364 ok
365 ok
366 ## END
367 ## N-I dash stdout: STDOUT
368 ## N-I dash stderr: STDERR
369 ## N-I dash status: 1
370
371 #### 1>&- to close file descriptor
372 exec 5> "$TMP/f.txt"
373 echo hello >&5
374 exec 5>&-
375 echo world >&5
376 cat "$TMP/f.txt"
377 ## stdout-json: "hello\n"
378
379 #### 1>&2- to move file descriptor
380 exec 5> "$TMP/f.txt"
381 echo hello5 >&5
382 exec 6>&5-
383 echo world5 >&5
384 echo world6 >&6
385 exec 6>&-
386 cat "$TMP/f.txt"
387 ## stdout-json: "hello5\nworld6\n"
388 ## N-I dash status: 2
389 ## N-I dash stdout-json: ""
390 ## N-I mksh status: 1
391 ## N-I mksh stdout-json: ""
392
393 #### 1>&2- (Bash bug: fail to restore closed fd)
394
395 # 7/2021: descriptor 8 is open on Github Actions, so use descriptor 6 instead
396
397 # Fix for CI systems where process state isn't clean: Close descriptors 6 and 7.
398 exec 6>&- 7>&-
399
400 opened=$(ls /proc/$$/fd)
401 if echo "$opened" | egrep '^7$'; then
402 echo "FD 7 shouldn't be open"
403 echo "OPENED:"
404 echo "$opened"
405 fi
406 if echo "$opened" | egrep '^6$'; then
407 echo "FD 6 shouldn't be open"
408 echo "OPENED:"
409 echo "$opened"
410 fi
411
412 exec 7> "$TMP/f.txt"
413 : 6>&7 7>&-
414 echo hello >&7
415 : 6>&7-
416 echo world >&7
417 exec 7>&-
418 cat "$TMP/f.txt"
419 ## status: 2
420 ## stdout-json: ""
421 ## OK mksh status: 1
422 ## BUG bash status: 0
423 ## BUG bash stdout: hello
424
425 #### <> for read/write
426 echo first >$TMP/rw.txt
427 exec 8<>$TMP/rw.txt
428 read line <&8
429 echo line=$line
430 echo second 1>&8
431 echo CONTENTS
432 cat $TMP/rw.txt
433 ## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
434
435 #### <> for read/write named pipes
436 rm -f "$TMP/f.pipe"
437 mkfifo "$TMP/f.pipe"
438 exec 8<> "$TMP/f.pipe"
439 echo first >&8
440 echo second >&8
441 read line1 <&8
442 read line2 <&8
443 exec 8<&-
444 echo line1=$line1 line2=$line2
445 ## stdout: line1=first line2=second
446
447 #### &>> appends stdout and stderr
448
449 # Fix for flaky tests: dash behaves non-deterministically under load! It
450 # doesn't implement the behavior anyway so I don't care why.
451 case $SH in
452 *dash)
453 exit 1
454 ;;
455 esac
456
457 echo "ok" > $TMP/f.txt
458 stdout_stderr.py &>> $TMP/f.txt
459 grep ok $TMP/f.txt >/dev/null && echo 'ok'
460 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
461 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
462 ## STDOUT:
463 ok
464 ok
465 ok
466 ## END
467 ## N-I dash stdout-json: ""
468 ## N-I dash status: 1
469
470 #### exec redirect then various builtins
471 exec 5>$TMP/log.txt
472 echo hi >&5
473 set -o >&5
474 echo done
475 ## STDOUT:
476 done
477 ## END
478
479 #### >$file touches a file
480 rm -f myfile
481 test -f myfile
482 echo status=$?
483 >myfile
484 test -f myfile
485 echo status=$?
486 ## STDOUT:
487 status=1
488 status=0
489 ## END
490 # regression for OSH
491 ## stderr-json: ""
492
493 #### $(< $file) yields the contents of the file
494
495 echo FOO > myfile
496 foo=$(< myfile)
497 echo $foo
498 ## STDOUT:
499 FOO
500 ## END
501 ## N-I dash/ash/yash stdout-json: "\n"
502
503 #### $(< file) with more statements
504
505 # note that it doesn't do this without a command sub!
506 # It's apparently a special case in bash, mksh, and zsh?
507 foo=$(echo begin; < myfile)
508 echo $foo
509 echo ---
510
511 foo=$(< myfile; echo end)
512 echo $foo
513 echo ---
514
515 foo=$(< myfile; <myfile)
516 echo $foo
517 echo ---
518
519 ## STDOUT:
520 begin
521 ---
522 end
523 ---
524
525 ---
526 ## END
527 # weird, zsh behaves differently
528 ## OK zsh STDOUT:
529 begin
530 FOO
531 ---
532 FOO
533 end
534 ---
535 FOO
536 FOO
537 ---
538 ## END
539
540
541 #### < file in pipeline and subshell doesn't work
542 echo FOO > file2
543
544 # This only happens in command subs, which is weird
545 < file2 | tr A-Z a-z
546 ( < file2 )
547 echo end
548 ## STDOUT:
549 end
550 ## END
551
552 #### 2>&1 with no command
553 ( exit 42 ) # status is reset after this
554 echo status=$?
555 2>&1
556 echo status=$?
557 ## STDOUT:
558 status=42
559 status=0
560 ## END
561 ## stderr-json: ""
562
563 #### 2&>1 (is it a redirect or is it like a&>1)
564 2&>1
565 echo status=$?
566 ## STDOUT:
567 status=127
568 ## END
569 ## OK mksh/dash STDOUT:
570 status=0
571 ## END
572
573 #### can't mention big file descriptor
574 echo hi 9>&1
575 # 23 is the max descriptor fo rmksh
576 #echo hi 24>&1
577 echo hi 99>&1
578 echo hi 100>&1
579 ## OK osh STDOUT:
580 hi
581 hi
582 hi 100
583 ## END
584 ## STDOUT:
585 hi
586 hi 99
587 hi 100
588 ## END
589 ## BUG bash STDOUT:
590 hi
591 hi
592 hi
593 ## END
594
595 #### : >/dev/null 2> / (OSH regression: fail to pop fd frame)
596 # oil 0.8.pre4 fails to restore fds after redirection failure. In the
597 # following case, the fd frame remains after the redirection failure
598 # "2> /" so that the effect of redirection ">/dev/null" remains after
599 # the completion of the command.
600 : >/dev/null 2> /
601 echo hello
602 ## stdout: hello
603 ## OK dash stdout-json: ""
604 ## OK dash status: 2
605 ## OK mksh stdout-json: ""
606 ## OK mksh status: 1
607 # dash/mksh terminates the execution of script on the redirection.
608
609 #### echo foo >&100 (OSH regression: does not fail with invalid fd 100)
610 # oil 0.8.pre4 does not fail with non-existent fd 100.
611 fd=100
612 echo foo >&$fd
613 ## stdout-json: ""
614 ## status: 1
615 ## OK dash status: 2
616
617 #### echo foo >&N where N is first unused fd
618 # 1. prepare default fd for internal uses
619 minfd=10
620 case ${SH##*/} in
621 (mksh) minfd=24 ;;
622 (osh) minfd=100 ;;
623 esac
624
625 # 2. prepare first unused fd
626 fd=$minfd
627 is-fd-open() { : >&$1; }
628 while is-fd-open "$fd"; do
629 : $((fd+=1))
630
631 # prevent infinite loop for broken osh_eval
632 if test $fd -gt 1000; then
633 break
634 fi
635 done
636
637 # 3. test
638 echo foo >&$fd
639 ## stdout-json: ""
640 ## status: 1
641 ## OK dash status: 2
642
643 #### exec {fd}>&- (OSH regression: fails to close fd)
644 # mksh, dash do not implement {fd} redirections.
645 case $SH in (mksh|dash) exit 1 ;; esac
646 # oil 0.8.pre4 fails to close fd by {fd}&-.
647 exec {fd}>file1
648 echo foo >&$fd
649 exec {fd}>&-
650 echo bar >&$fd
651 cat file1
652 ## stdout: foo
653 ## N-I mksh/dash stdout-json: ""
654 ## N-I mksh/dash status: 1