1 ## oils_failures_allowed: 2
2 ## compare_shells: bash dash mksh
3
4 #### >&
5 echo hi 1>&2
6 ## stderr: hi
7
8 #### <&
9 # Is there a simpler test case for this?
10 echo foo > $TMP/lessamp.txt
11 exec 6< $TMP/lessamp.txt
12 read line <&6
13 echo "[$line]"
14 ## stdout: [foo]
15
16 #### Leading redirect
17 echo hello >$TMP/hello.txt # temporary fix
18 <$TMP/hello.txt cat
19 ## stdout: hello
20
21 #### Nonexistent file
22 cat <$TMP/nonexistent.txt
23 echo status=$?
24 ## stdout: status=1
25 ## OK dash stdout: status=2
26
27 #### Redirect in command sub
28 FOO=$(echo foo 1>&2)
29 echo $FOO
30 ## stdout:
31 ## stderr: foo
32
33 #### Redirect in assignment
34 # dash captures stderr to a file here, which seems correct. Bash doesn't and
35 # just lets it go to actual stderr.
36 # For now we agree with dash/mksh, since it involves fewer special cases in the
37 # code.
38
39 FOO=$(echo foo 1>&2) 2>$TMP/no-command.txt
40 echo FILE=
41 cat $TMP/no-command.txt
42 echo "FOO=$FOO"
43 ## STDOUT:
44 FILE=
45 foo
46 FOO=
47 ## END
48 ## BUG bash STDOUT:
49 FILE=
50 FOO=
51 ## END
52
53 #### Redirect in function body.
54 fun() { echo hi; } 1>&2
55 fun
56 ## STDOUT:
57 ## END
58 ## STDERR:
59 hi
60 ## END
61
62 #### Redirect in function body is evaluated multiple times
63 i=0
64 fun() { echo "file $i"; } 1> "$TMP/file$((i++))"
65 fun
66 fun
67 echo i=$i
68 echo __
69 cat $TMP/file0
70 echo __
71 cat $TMP/file1
72 ## STDOUT:
73 i=2
74 __
75 file 1
76 __
77 file 2
78 ## END
79 ## N-I dash stdout-json: ""
80 ## N-I dash status: 2
81
82 #### Redirect in function body AND function call
83 fun() { echo hi; } 1>&2
84 fun 2>&1
85 ## stdout-json: "hi\n"
86 ## stderr-json: ""
87
88 #### Descriptor redirect with spaces
89 # Hm this seems like a failure of lookahead! The second thing should look to a
90 # file-like thing.
91 # I think this is a posix issue.
92 # tag: posix-issue
93 echo one 1>&2
94 echo two 1 >&2
95 echo three 1>& 2
96 ## stderr-json: "one\ntwo 1\nthree\n"
97
98 #### Filename redirect with spaces
99 # This time 1 *is* a descriptor, not a word. If you add a space between 1 and
100 # >, it doesn't work.
101 echo two 1> $TMP/file-redir1.txt
102 cat $TMP/file-redir1.txt
103 ## stdout: two
104
105 #### Quoted filename redirect with spaces
106 # POSIX makes node of this
107 echo two \1 > $TMP/file-redir2.txt
108 cat $TMP/file-redir2.txt
109 ## stdout: two 1
110
111 #### Descriptor redirect with filename
112 # bash/mksh treat this like a filename, not a descriptor.
113 # dash aborts.
114 echo one 1>&$TMP/nonexistent-filename__
115 echo "status=$?"
116 ## stdout: status=1
117 ## BUG bash stdout: status=0
118 ## OK dash stdout-json: ""
119 ## OK dash status: 2
120
121 #### redirect for loop
122 for i in $(seq 3)
123 do
124 echo $i
125 done > $TMP/redirect-for-loop.txt
126 cat $TMP/redirect-for-loop.txt
127 ## stdout-json: "1\n2\n3\n"
128
129 #### redirect subshell
130 ( echo foo ) 1>&2
131 ## stderr: foo
132 ## stdout-json: ""
133
134 #### Prefix redirect for loop -- not allowed
135 >$TMP/redirect2.txt for i in $(seq 3)
136 do
137 echo $i
138 done
139 cat $TMP/redirect2.txt
140 ## status: 2
141 ## OK mksh status: 1
142
143 #### Brace group redirect
144 # Suffix works, but prefix does NOT work.
145 # That comes from '| compound_command redirect_list' in the grammar!
146 { echo block-redirect; } > $TMP/br.txt
147 cat $TMP/br.txt | wc -c
148 ## stdout: 15
149
150 #### Redirect echo to stderr, and then redirect all of stdout somewhere.
151 { echo foo 1>&2; echo 012345789; } > $TMP/block-stdout.txt
152 cat $TMP/block-stdout.txt | wc -c
153 ## stderr: foo
154 ## stdout: 10
155
156 #### Redirect in the middle of two assignments
157 FOO=foo >$TMP/out.txt BAR=bar printenv.py FOO BAR
158 tac $TMP/out.txt
159 ## stdout-json: "bar\nfoo\n"
160
161 #### Redirect in the middle of a command
162 f=$TMP/out
163 echo -n 1 2 '3 ' > $f
164 echo -n 4 5 >> $f '6 '
165 echo -n 7 >> $f 8 '9 '
166 echo -n >> $f 1 2 '3 '
167 echo >> $f -n 4 5 '6 '
168 cat $f
169 ## stdout-json: "1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 "
170
171 #### Named file descriptor
172 exec {myfd}> $TMP/named-fd.txt
173 echo named-fd-contents >& $myfd
174 cat $TMP/named-fd.txt
175 ## stdout: named-fd-contents
176 ## status: 0
177 ## N-I dash/mksh stdout-json: ""
178 ## N-I dash/mksh status: 127
179
180 #### Double digit fd (20> file)
181 exec 20> "$TMP/double-digit-fd.txt"
182 echo hello20 >&20
183 cat "$TMP/double-digit-fd.txt"
184 ## stdout: hello20
185 ## BUG dash stdout-json: ""
186 ## BUG dash status: 127
187
188 #### : 9> fdleak (OSH regression)
189 true 9> "$TMP/fd.txt"
190 ( echo world >&9 )
191 cat "$TMP/fd.txt"
192 ## stdout-json: ""
193
194 #### : 3>&3 (OSH regression)
195
196 # mksh started being flaky on the continuous build and during release. We
197 # don't care! Related to issue #330.
198 case $SH in (mksh) exit ;; esac
199
200 : 3>&3
201 echo hello
202 ## stdout: hello
203 ## BUG mksh stdout-json: ""
204 ## BUG mksh status: 0
205
206 #### : 3>&3-
207 : 3>&3-
208 echo hello
209 ## stdout: hello
210 ## N-I dash/mksh stdout-json: ""
211 ## N-I mksh status: 1
212 ## N-I dash status: 2
213
214 #### 3>&- << EOF (OSH regression: fail to restore fds)
215 exec 3> "$TMP/fd.txt"
216 echo hello 3>&- << EOF
217 EOF
218 echo world >&3
219 exec 3>&- # close
220 cat "$TMP/fd.txt"
221 ## STDOUT:
222 hello
223 world
224 ## END
225
226 #### Open file on descriptor 3 and write to it many times
227
228 # different than case below because 3 is the likely first FD of open()
229
230 exec 3> "$TMP/fd3.txt"
231 echo hello >&3
232 echo world >&3
233 exec 3>&- # close
234 cat "$TMP/fd3.txt"
235 ## STDOUT:
236 hello
237 world
238 ## END
239
240 #### Open file on descriptor 4 and write to it many times
241
242 # different than the case above because because 4 isn't the likely first FD
243
244 exec 4> "$TMP/fd4.txt"
245 echo hello >&4
246 echo world >&4
247 exec 4>&- # close
248 cat "$TMP/fd4.txt"
249 ## STDOUT:
250 hello
251 world
252 ## END
253
254 #### Redirect function stdout
255 f() { echo one; echo two; }
256 f > $TMP/redirect-func.txt
257 cat $TMP/redirect-func.txt
258 ## stdout-json: "one\ntwo\n"
259
260 #### Nested function stdout redirect
261 # Shows that a stack is necessary.
262 inner() {
263 echo i1
264 echo i2
265 }
266 outer() {
267 echo o1
268 inner > $TMP/inner.txt
269 echo o2
270 }
271 outer > $TMP/outer.txt
272 cat $TMP/inner.txt
273 echo --
274 cat $TMP/outer.txt
275 ## stdout-json: "i1\ni2\n--\no1\no2\n"
276
277 #### Redirect to empty string
278 f=''
279 echo s > "$f"
280 echo "result=$?"
281 set -o errexit
282 echo s > "$f"
283 echo DONE
284 ## stdout: result=1
285 ## status: 1
286 ## OK dash stdout: result=2
287 ## OK dash status: 2
288
289 #### Redirect to file descriptor that's not open
290 # Notes:
291 # - 7/2021: descriptor 7 seems to work on all CI systems. The process state
292 # isn't clean, but we could probably close it in OSH?
293 # - dash doesn't allow file descriptors greater than 9. (This is a good
294 # thing, because the bash chapter in AOSA book mentions that juggling user
295 # vs. system file descriptors is a huge pain.)
296 # - But somehow running in parallel under spec-runner.sh changes whether
297 # descriptor 3 is open. e.g. 'echo hi 1>&3'. Possibly because of
298 # /usr/bin/time. The _tmp/spec/*.task.txt file gets corrupted!
299 # - Oh this is because I use time --output-file. That opens descriptor 3. And
300 # then time forks the shell script. The file descriptor table is inherited.
301 # - You actually have to set the file descriptor to something. What do
302 # configure and debootstrap too?
303
304 opened=$(ls /proc/$$/fd)
305 if echo "$opened" | egrep '^7$'; then
306 echo "FD 7 shouldn't be open"
307 echo "OPENED:"
308 echo "$opened"
309 fi
310
311 echo hi 1>&7
312 ## stdout-json: ""
313 ## status: 1
314 ## OK dash status: 2
315
316 #### Open descriptor with exec
317 # What is the point of this? ./configure scripts and debootstrap use it.
318 exec 3>&1
319 echo hi 1>&3
320 ## stdout: hi
321 ## status: 0
322
323 #### Open multiple descriptors with exec
324 # What is the point of this? ./configure scripts and debootstrap use it.
325 exec 3>&1
326 exec 4>&1
327 echo three 1>&3
328 echo four 1>&4
329 ## stdout-json: "three\nfour\n"
330 ## status: 0
331
332 #### >| to clobber
333 echo XX >| $TMP/c.txt
334
335 set -o noclobber
336
337 echo YY > $TMP/c.txt # not clobber
338 echo status=$?
339
340 cat $TMP/c.txt
341 echo ZZ >| $TMP/c.txt
342
343 cat $TMP/c.txt
344 ## STDOUT:
345 status=1
346 XX
347 ZZ
348 ## END
349 ## OK dash STDOUT:
350 status=2
351 XX
352 ZZ
353 ## END
354
355 #### &> redirects stdout and stderr
356 tmp="$(basename $SH)-$$.txt" # unique name for shell and test case
357 #echo $tmp
358
359 stdout_stderr.py &> $tmp
360
361 # order is indeterminate
362 grep STDOUT $tmp
363 grep STDERR $tmp
364
365 ## STDOUT:
366 STDOUT
367 STDERR
368 ## END
369 ## N-I dash stdout: STDOUT
370 ## N-I dash stderr: STDERR
371 ## N-I dash status: 1
372
373 #### >&word redirects stdout and stderr when word is not a number or -
374
375 # dash, mksh don't implement this bash behaviour.
376 case $SH in (dash|mksh) exit 1 ;; esac
377
378 tmp="$(basename $SH)-$$.txt" # unique name for shell and test case
379
380 stdout_stderr.py >&$tmp
381
382 # order is indeterminate
383 grep STDOUT $tmp
384 grep STDERR $tmp
385
386 ## STDOUT:
387 STDOUT
388 STDERR
389 ## END
390 ## N-I dash/mksh status: 1
391 ## N-I dash/mksh stdout-json: ""
392
393 #### 1>&- to close file descriptor
394 exec 5> "$TMP/f.txt"
395 echo hello >&5
396 exec 5>&-
397 echo world >&5
398 cat "$TMP/f.txt"
399 ## stdout-json: "hello\n"
400
401 #### 1>&2- to move file descriptor
402 exec 5> "$TMP/f.txt"
403 echo hello5 >&5
404 exec 6>&5-
405 echo world5 >&5
406 echo world6 >&6
407 exec 6>&-
408 cat "$TMP/f.txt"
409 ## stdout-json: "hello5\nworld6\n"
410 ## N-I dash status: 2
411 ## N-I dash stdout-json: ""
412 ## N-I mksh status: 1
413 ## N-I mksh stdout-json: ""
414
415 #### 1>&2- (Bash bug: fail to restore closed fd)
416
417 # 7/2021: descriptor 8 is open on Github Actions, so use descriptor 6 instead
418
419 # Fix for CI systems where process state isn't clean: Close descriptors 6 and 7.
420 exec 6>&- 7>&-
421
422 opened=$(ls /proc/$$/fd)
423 if echo "$opened" | egrep '^7$'; then
424 echo "FD 7 shouldn't be open"
425 echo "OPENED:"
426 echo "$opened"
427 fi
428 if echo "$opened" | egrep '^6$'; then
429 echo "FD 6 shouldn't be open"
430 echo "OPENED:"
431 echo "$opened"
432 fi
433
434 exec 7> "$TMP/f.txt"
435 : 6>&7 7>&-
436 echo hello >&7
437 : 6>&7-
438 echo world >&7
439 exec 7>&-
440 cat "$TMP/f.txt"
441
442 ## status: 1
443 ## stdout-json: ""
444
445 ## OK dash status: 2
446
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 # trivia: 23 is the max descriptor for mksh
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