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 : 3>&3
199 echo hello
200 ## stdout: hello
201 ## BUG mksh stdout-json: ""
202 ## BUG mksh status: 1
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 # - dash doesn't allow file descriptors greater than 9. (This is a good
290 # thing, because the bash chapter in AOSA book mentions that juggling user
291 # vs. system file descriptors is a huge pain.)
292 # - But somehow running in parallel under spec-runner.sh changes whether
293 # descriptor 3 is open. e.g. 'echo hi 1>&3'. Possibly because of
294 # /usr/bin/time. The _tmp/spec/*.task.txt file gets corrupted!
295 # - Oh this is because I use time --output-file. That opens descriptor 3. And
296 # then time forks the shell script. The file descriptor table is inherited.
297 # - You actually have to set the file descriptor to something. What do
298 # configure and debootstrap too?
299
300 # 3/2020 note: file descriptor 9 failed on Travis, so I changed it to 8. The
301 # process state isn't necessarly clean. TODO: Close the descriptor when OSH
302 # supports it?
303
304 echo hi 1>&8
305 ## status: 1
306 ## OK dash status: 2
307
308 #### Open descriptor with exec
309 # What is the point of this? ./configure scripts and debootstrap use it.
310 exec 3>&1
311 echo hi 1>&3
312 ## stdout: hi
313 ## status: 0
314
315 #### Open multiple descriptors with exec
316 # What is the point of this? ./configure scripts and debootstrap use it.
317 exec 3>&1
318 exec 4>&1
319 echo three 1>&3
320 echo four 1>&4
321 ## stdout-json: "three\nfour\n"
322 ## status: 0
323
324 #### >| to clobber
325 echo XX >| $TMP/c.txt
326
327 set -o noclobber
328
329 echo YY > $TMP/c.txt # not globber
330 echo status=$?
331
332 cat $TMP/c.txt
333 echo ZZ >| $TMP/c.txt
334
335 cat $TMP/c.txt
336 ## STDOUT:
337 status=1
338 XX
339 ZZ
340 ## END
341 ## OK dash STDOUT:
342 status=2
343 XX
344 ZZ
345 ## END
346
347 #### &> redirects stdout and stderr
348 stdout_stderr.py &> $TMP/f.txt
349 # order is indeterminate
350 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
351 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
352 ## STDOUT:
353 ok
354 ok
355 ## END
356 ## N-I dash stdout: STDOUT
357 ## N-I dash stderr: STDERR
358 ## N-I dash status: 1
359
360 #### 1>&- to close file descriptor
361 exec 5> "$TMP/f.txt"
362 echo hello >&5
363 exec 5>&-
364 echo world >&5
365 cat "$TMP/f.txt"
366 ## stdout-json: "hello\n"
367
368 #### 1>&2- to move file descriptor
369 exec 5> "$TMP/f.txt"
370 echo hello5 >&5
371 exec 6>&5-
372 echo world5 >&5
373 echo world6 >&6
374 exec 6>&-
375 cat "$TMP/f.txt"
376 ## stdout-json: "hello5\nworld6\n"
377 ## N-I dash status: 2
378 ## N-I dash stdout-json: ""
379 ## N-I mksh status: 1
380 ## N-I mksh stdout-json: ""
381
382 #### 1>&2- (Bash bug: fail to restore closed fd)
383 exec 7> "$TMP/f.txt"
384 : 8>&7 7>&-
385 echo hello >&7
386 : 8>&7-
387 echo world >&7
388 exec 7>&-
389 cat "$TMP/f.txt"
390 ## status: 2
391 ## stdout-json: ""
392 ## OK mksh status: 1
393 ## BUG bash status: 0
394 ## BUG bash stdout: hello
395
396 #### <> for read/write
397 echo first >$TMP/rw.txt
398 exec 8<>$TMP/rw.txt
399 read line <&8
400 echo line=$line
401 echo second 1>&8
402 echo CONTENTS
403 cat $TMP/rw.txt
404 ## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
405
406 #### <> for read/write named pipes
407 rm -f "$TMP/f.pipe"
408 mkfifo "$TMP/f.pipe"
409 exec 8<> "$TMP/f.pipe"
410 echo first >&8
411 echo second >&8
412 read line1 <&8
413 read line2 <&8
414 exec 8<&-
415 echo line1=$line1 line2=$line2
416 ## stdout: line1=first line2=second
417
418 #### &>> appends stdout and stderr
419
420 # Fix for flaky tests: dash behaves non-deterministically under load! It
421 # doesn't implement the behavior anyway so I don't care why.
422 case $SH in
423 *dash)
424 exit 1
425 ;;
426 esac
427
428 echo "ok" > $TMP/f.txt
429 stdout_stderr.py &>> $TMP/f.txt
430 grep ok $TMP/f.txt >/dev/null && echo 'ok'
431 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
432 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
433 ## STDOUT:
434 ok
435 ok
436 ok
437 ## END
438 ## N-I dash stdout-json: ""
439 ## N-I dash status: 1
440
441 #### exec redirect then various builtins
442 exec 5>$TMP/log.txt
443 echo hi >&5
444 set -o >&5
445 echo done
446 ## STDOUT:
447 done
448 ## END
449
450 #### >$file touches a file
451 rm -f myfile
452 test -f myfile
453 echo status=$?
454 >myfile
455 test -f myfile
456 echo status=$?
457 ## STDOUT:
458 status=1
459 status=0
460 ## END
461 # regression for OSH
462 ## stderr-json: ""
463
464 #### $(< $file) yields the contents of the file
465
466 echo FOO > myfile
467 foo=$(< myfile)
468 echo $foo
469 ## STDOUT:
470 FOO
471 ## END
472 ## N-I dash/ash/yash stdout-json: "\n"
473
474 #### $(< file) with more statements
475
476 # note that it doesn't do this without a command sub!
477 # It's apparently a special case in bash, mksh, and zsh?
478 foo=$(echo begin; < myfile)
479 echo $foo
480 echo ---
481
482 foo=$(< myfile; echo end)
483 echo $foo
484 echo ---
485
486 foo=$(< myfile; <myfile)
487 echo $foo
488 echo ---
489
490 ## STDOUT:
491 begin
492 ---
493 end
494 ---
495
496 ---
497 ## END
498 # weird, zsh behaves differently
499 ## OK zsh STDOUT:
500 begin
501 FOO
502 ---
503 FOO
504 end
505 ---
506 FOO
507 FOO
508 ---
509 ## END
510
511
512 #### < file in pipeline and subshell doesn't work
513 echo FOO > file2
514
515 # This only happens in command subs, which is weird
516 < file2 | tr A-Z a-z
517 ( < file2 )
518 echo end
519 ## STDOUT:
520 end
521 ## END
522
523 #### 2>&1 with no command
524 ( exit 42 ) # status is reset after this
525 echo status=$?
526 2>&1
527 echo status=$?
528 ## STDOUT:
529 status=42
530 status=0
531 ## END
532 ## stderr-json: ""
533
534 #### 2&>1 (is it a redirect or is it like a&>1)
535 2&>1
536 echo status=$?
537 ## STDOUT:
538 status=127
539 ## END
540 ## OK mksh/dash STDOUT:
541 status=0
542 ## END
543
544 #### can't mention big file descriptor
545 echo hi 9>&1
546 # 23 is the max descriptor fo rmksh
547 #echo hi 24>&1
548 echo hi 99>&1
549 echo hi 100>&1
550 ## OK osh STDOUT:
551 hi
552 hi
553 hi 100
554 ## END
555 ## STDOUT:
556 hi
557 hi 99
558 hi 100
559 ## END
560 ## BUG bash STDOUT:
561 hi
562 hi
563 hi
564 ## END
565
566 #### : >/dev/null 2> / (OSH regression: fail to pop fd frame)
567 # oil 0.8.pre4 fails to restore fds after redirection failure. In the
568 # following case, the fd frame remains after the redirection failure
569 # "2> /" so that the effect of redirection ">/dev/null" remains after
570 # the completion of the command.
571 : >/dev/null 2> /
572 echo hello
573 ## stdout: hello
574 ## OK dash stdout-json: ""
575 ## OK dash status: 2
576 ## OK mksh stdout-json: ""
577 ## OK mksh status: 1
578 # dash/mksh terminates the execution of script on the redirection.
579
580 #### echo foo >&100 (OSH regression: does not fail with invalid fd 100)
581 # oil 0.8.pre4 does not fail with non-existent fd 100.
582 fd=100
583 echo foo >&$fd
584 ## stdout-json: ""
585 ## status: 1
586 ## OK dash status: 2
587
588 #### echo foo >&N where N is first unused fd
589 # 1. prepare default fd for internal uses
590 minfd=10
591 case ${SH##*/} in
592 (mksh) minfd=24 ;;
593 (osh) minfd=100 ;;
594 esac
595
596 # 2. prepare first unused fd
597 fd=$minfd
598 is-fd-open() { : >&$1; }
599 while is-fd-open "$fd"; do
600 : $((fd+=1))
601
602 # prevent infinite loop for broken osh_eval
603 if test $fd -gt 1000; then
604 break
605 fi
606 done
607
608 # 3. test
609 echo foo >&$fd
610 ## stdout-json: ""
611 ## status: 1
612 ## OK dash status: 2
613
614 #### exec {fd}>&- (OSH regression: fails to close fd)
615 # mksh, dash do not implement {fd} redirections.
616 case $SH in (mksh|dash) exit 1 ;; esac
617 # oil 0.8.pre4 fails to close fd by {fd}&-.
618 exec {fd}>file1
619 echo foo >&$fd
620 exec {fd}>&-
621 echo bar >&$fd
622 cat file1
623 ## stdout: foo
624 ## N-I mksh/dash stdout-json: ""
625 ## N-I mksh/dash status: 1