OILS / spec / redirect.test.sh View on Github | oilshell.org

677 lines, 319 significant
1
2#### >&
3echo hi 1>&2
4## stderr: hi
5
6#### <&
7# Is there a simpler test case for this?
8echo foo > $TMP/lessamp.txt
9exec 6< $TMP/lessamp.txt
10read line <&6
11echo "[$line]"
12## stdout: [foo]
13
14#### Leading redirect
15echo hello >$TMP/hello.txt # temporary fix
16<$TMP/hello.txt cat
17## stdout: hello
18
19#### Nonexistent file
20cat <$TMP/nonexistent.txt
21echo status=$?
22## stdout: status=1
23## OK dash stdout: status=2
24
25#### Redirect in command sub
26FOO=$(echo foo 1>&2)
27echo $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
37FOO=$(echo foo 1>&2) 2>$TMP/no-command.txt
38echo FILE=
39cat $TMP/no-command.txt
40echo "FOO=$FOO"
41## STDOUT:
42FILE=
43foo
44FOO=
45## END
46## BUG bash STDOUT:
47FILE=
48FOO=
49## END
50
51#### Redirect in function body.
52fun() { echo hi; } 1>&2
53fun
54## STDOUT:
55## END
56## STDERR:
57hi
58## END
59
60#### Redirect in function body is evaluated multiple times
61i=0
62fun() { echo "file $i"; } 1> "$TMP/file$((i++))"
63fun
64fun
65echo i=$i
66echo __
67cat $TMP/file0
68echo __
69cat $TMP/file1
70## STDOUT:
71i=2
72__
73file 1
74__
75file 2
76## END
77## N-I dash stdout-json: ""
78## N-I dash status: 2
79
80#### Redirect in function body AND function call
81fun() { echo hi; } 1>&2
82fun 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
91echo one 1>&2
92echo two 1 >&2
93echo 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.
99echo two 1> $TMP/file-redir1.txt
100cat $TMP/file-redir1.txt
101## stdout: two
102
103#### Quoted filename redirect with spaces
104# POSIX makes node of this
105echo two \1 > $TMP/file-redir2.txt
106cat $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.
112echo one 1>&$TMP/nonexistent-filename__
113echo "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
120for i in $(seq 3)
121do
122 echo $i
123done > $TMP/redirect-for-loop.txt
124cat $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)
134do
135 echo $i
136done
137cat $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
145cat $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
150cat $TMP/block-stdout.txt | wc -c
151## stderr: foo
152## stdout: 10
153
154#### Redirect in the middle of two assignments
155FOO=foo >$TMP/out.txt BAR=bar printenv.py FOO BAR
156tac $TMP/out.txt
157## stdout-json: "bar\nfoo\n"
158
159#### Redirect in the middle of a command
160f=$TMP/out
161echo -n 1 2 '3 ' > $f
162echo -n 4 5 >> $f '6 '
163echo -n 7 >> $f 8 '9 '
164echo -n >> $f 1 2 '3 '
165echo >> $f -n 4 5 '6 '
166cat $f
167## stdout-json: "1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 "
168
169#### Named file descriptor
170exec {myfd}> $TMP/named-fd.txt
171echo named-fd-contents >& $myfd
172cat $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)
179exec 20> "$TMP/double-digit-fd.txt"
180echo hello20 >&20
181cat "$TMP/double-digit-fd.txt"
182## stdout: hello20
183## BUG dash stdout-json: ""
184## BUG dash status: 127
185
186#### : 9> fdleak (OSH regression)
187true 9> "$TMP/fd.txt"
188( echo world >&9 )
189cat "$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.
196case $SH in (mksh) exit ;; esac
197
198: 3>&3
199echo hello
200## stdout: hello
201## BUG mksh stdout-json: ""
202## BUG mksh status: 0
203
204#### : 3>&3-
205: 3>&3-
206echo 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)
213exec 3> "$TMP/fd.txt"
214echo hello 3>&- << EOF
215EOF
216echo world >&3
217exec 3>&- # close
218cat "$TMP/fd.txt"
219## STDOUT:
220hello
221world
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
228exec 3> "$TMP/fd3.txt"
229echo hello >&3
230echo world >&3
231exec 3>&- # close
232cat "$TMP/fd3.txt"
233## STDOUT:
234hello
235world
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
242exec 4> "$TMP/fd4.txt"
243echo hello >&4
244echo world >&4
245exec 4>&- # close
246cat "$TMP/fd4.txt"
247## STDOUT:
248hello
249world
250## END
251
252#### Redirect function stdout
253f() { echo one; echo two; }
254f > $TMP/redirect-func.txt
255cat $TMP/redirect-func.txt
256## stdout-json: "one\ntwo\n"
257
258#### Nested function stdout redirect
259# Shows that a stack is necessary.
260inner() {
261 echo i1
262 echo i2
263}
264outer() {
265 echo o1
266 inner > $TMP/inner.txt
267 echo o2
268}
269outer > $TMP/outer.txt
270cat $TMP/inner.txt
271echo --
272cat $TMP/outer.txt
273## stdout-json: "i1\ni2\n--\no1\no2\n"
274
275#### Redirect to empty string
276f=''
277echo s > "$f"
278echo "result=$?"
279set -o errexit
280echo s > "$f"
281echo 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
302opened=$(ls /proc/$$/fd)
303if echo "$opened" | egrep '^7$'; then
304 echo "FD 7 shouldn't be open"
305 echo "OPENED:"
306 echo "$opened"
307fi
308
309echo 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.
316exec 3>&1
317echo 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.
323exec 3>&1
324exec 4>&1
325echo three 1>&3
326echo four 1>&4
327## stdout-json: "three\nfour\n"
328## status: 0
329
330#### >| to clobber
331echo XX >| $TMP/c.txt
332
333set -o noclobber
334
335echo YY > $TMP/c.txt # not clobber
336echo status=$?
337
338cat $TMP/c.txt
339echo ZZ >| $TMP/c.txt
340
341cat $TMP/c.txt
342## STDOUT:
343status=1
344XX
345ZZ
346## END
347## OK dash STDOUT:
348status=2
349XX
350ZZ
351## END
352
353#### &> redirects stdout and stderr
354tmp="$(basename $SH)-$$.txt" # unique name for shell and test case
355#echo $tmp
356
357stdout_stderr.py &> $tmp
358
359# order is indeterminate
360grep STDOUT $tmp
361grep STDERR $tmp
362
363## STDOUT:
364STDOUT
365STDERR
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.
374case $SH in (dash|mksh) exit 1 ;; esac
375
376tmp="$(basename $SH)-$$.txt" # unique name for shell and test case
377
378stdout_stderr.py >&$tmp
379
380# order is indeterminate
381grep STDOUT $tmp
382grep STDERR $tmp
383
384## STDOUT:
385STDOUT
386STDERR
387## END
388## N-I dash/mksh status: 1
389## N-I dash/mksh stdout-json: ""
390
391#### 1>&- to close file descriptor
392exec 5> "$TMP/f.txt"
393echo hello >&5
394exec 5>&-
395echo world >&5
396cat "$TMP/f.txt"
397## stdout-json: "hello\n"
398
399#### 1>&2- to move file descriptor
400exec 5> "$TMP/f.txt"
401echo hello5 >&5
402exec 6>&5-
403echo world5 >&5
404echo world6 >&6
405exec 6>&-
406cat "$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.
418exec 6>&- 7>&-
419
420opened=$(ls /proc/$$/fd)
421if echo "$opened" | egrep '^7$'; then
422 echo "FD 7 shouldn't be open"
423 echo "OPENED:"
424 echo "$opened"
425fi
426if echo "$opened" | egrep '^6$'; then
427 echo "FD 6 shouldn't be open"
428 echo "OPENED:"
429 echo "$opened"
430fi
431
432exec 7> "$TMP/f.txt"
433: 6>&7 7>&-
434echo hello >&7
435: 6>&7-
436echo world >&7
437exec 7>&-
438cat "$TMP/f.txt"
439
440## status: 1
441## stdout-json: ""
442
443## OK dash status: 2
444
445## BUG bash status: 0
446## BUG bash stdout: hello
447
448#### <> for read/write
449echo first >$TMP/rw.txt
450exec 8<>$TMP/rw.txt
451read line <&8
452echo line=$line
453echo second 1>&8
454echo CONTENTS
455cat $TMP/rw.txt
456## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
457
458#### <> for read/write named pipes
459rm -f "$TMP/f.pipe"
460mkfifo "$TMP/f.pipe"
461exec 8<> "$TMP/f.pipe"
462echo first >&8
463echo second >&8
464read line1 <&8
465read line2 <&8
466exec 8<&-
467echo line1=$line1 line2=$line2
468## stdout: line1=first line2=second
469
470#### &>> appends stdout and stderr
471
472# Fix for flaky tests: dash behaves non-deterministically under load! It
473# doesn't implement the behavior anyway so I don't care why.
474case $SH in
475 *dash)
476 exit 1
477 ;;
478esac
479
480echo "ok" > $TMP/f.txt
481stdout_stderr.py &>> $TMP/f.txt
482grep ok $TMP/f.txt >/dev/null && echo 'ok'
483grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
484grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
485## STDOUT:
486ok
487ok
488ok
489## END
490## N-I dash stdout-json: ""
491## N-I dash status: 1
492
493#### exec redirect then various builtins
494exec 5>$TMP/log.txt
495echo hi >&5
496set -o >&5
497echo done
498## STDOUT:
499done
500## END
501
502#### >$file touches a file
503rm -f myfile
504test -f myfile
505echo status=$?
506>myfile
507test -f myfile
508echo status=$?
509## STDOUT:
510status=1
511status=0
512## END
513# regression for OSH
514## stderr-json: ""
515
516#### $(< $file) yields the contents of the file
517
518echo FOO > myfile
519foo=$(< myfile)
520echo $foo
521## STDOUT:
522FOO
523## END
524## N-I dash/ash/yash stdout-json: "\n"
525
526#### $(< file) with more statements
527
528# note that it doesn't do this without a command sub!
529# It's apparently a special case in bash, mksh, and zsh?
530foo=$(echo begin; < myfile)
531echo $foo
532echo ---
533
534foo=$(< myfile; echo end)
535echo $foo
536echo ---
537
538foo=$(< myfile; <myfile)
539echo $foo
540echo ---
541
542## STDOUT:
543begin
544---
545end
546---
547
548---
549## END
550# weird, zsh behaves differently
551## OK zsh STDOUT:
552begin
553FOO
554---
555FOO
556end
557---
558FOO
559FOO
560---
561## END
562
563
564#### < file in pipeline and subshell doesn't work
565echo FOO > file2
566
567# This only happens in command subs, which is weird
568< file2 | tr A-Z a-z
569( < file2 )
570echo end
571## STDOUT:
572end
573## END
574
575#### 2>&1 with no command
576( exit 42 ) # status is reset after this
577echo status=$?
5782>&1
579echo status=$?
580## STDOUT:
581status=42
582status=0
583## END
584## stderr-json: ""
585
586#### 2&>1 (is it a redirect or is it like a&>1)
5872&>1
588echo status=$?
589## STDOUT:
590status=127
591## END
592## OK mksh/dash STDOUT:
593status=0
594## END
595
596#### can't mention big file descriptor
597echo hi 9>&1
598# trivia: 23 is the max descriptor for mksh
599#echo hi 24>&1
600echo hi 99>&1
601echo hi 100>&1
602## OK osh STDOUT:
603hi
604hi
605hi 100
606## END
607## STDOUT:
608hi
609hi 99
610hi 100
611## END
612## BUG bash STDOUT:
613hi
614hi
615hi
616## END
617
618#### : >/dev/null 2> / (OSH regression: fail to pop fd frame)
619# oil 0.8.pre4 fails to restore fds after redirection failure. In the
620# following case, the fd frame remains after the redirection failure
621# "2> /" so that the effect of redirection ">/dev/null" remains after
622# the completion of the command.
623: >/dev/null 2> /
624echo hello
625## stdout: hello
626## OK dash stdout-json: ""
627## OK dash status: 2
628## OK mksh stdout-json: ""
629## OK mksh status: 1
630# dash/mksh terminates the execution of script on the redirection.
631
632#### echo foo >&100 (OSH regression: does not fail with invalid fd 100)
633# oil 0.8.pre4 does not fail with non-existent fd 100.
634fd=100
635echo foo >&$fd
636## stdout-json: ""
637## status: 1
638## OK dash status: 2
639
640#### echo foo >&N where N is first unused fd
641# 1. prepare default fd for internal uses
642minfd=10
643case ${SH##*/} in
644(mksh) minfd=24 ;;
645(osh) minfd=100 ;;
646esac
647
648# 2. prepare first unused fd
649fd=$minfd
650is-fd-open() { : >&$1; }
651while is-fd-open "$fd"; do
652 : $((fd+=1))
653
654 # prevent infinite loop for broken oils-for-unix
655 if test $fd -gt 1000; then
656 break
657 fi
658done
659
660# 3. test
661echo foo >&$fd
662## stdout-json: ""
663## status: 1
664## OK dash status: 2
665
666#### exec {fd}>&- (OSH regression: fails to close fd)
667# mksh, dash do not implement {fd} redirections.
668case $SH in (mksh|dash) exit 1 ;; esac
669# oil 0.8.pre4 fails to close fd by {fd}&-.
670exec {fd}>file1
671echo foo >&$fd
672exec {fd}>&-
673echo bar >&$fd
674cat file1
675## stdout: foo
676## N-I mksh/dash stdout-json: ""
677## N-I mksh/dash status: 1