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