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

679 lines, 319 significant
1## oils_failures_allowed: 2
2## compare_shells: bash dash mksh
3
4#### >&
5echo hi 1>&2
6## stderr: hi
7
8#### <&
9# Is there a simpler test case for this?
10echo foo > $TMP/lessamp.txt
11exec 6< $TMP/lessamp.txt
12read line <&6
13echo "[$line]"
14## stdout: [foo]
15
16#### Leading redirect
17echo hello >$TMP/hello.txt # temporary fix
18<$TMP/hello.txt cat
19## stdout: hello
20
21#### Nonexistent file
22cat <$TMP/nonexistent.txt
23echo status=$?
24## stdout: status=1
25## OK dash stdout: status=2
26
27#### Redirect in command sub
28FOO=$(echo foo 1>&2)
29echo $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
39FOO=$(echo foo 1>&2) 2>$TMP/no-command.txt
40echo FILE=
41cat $TMP/no-command.txt
42echo "FOO=$FOO"
43## STDOUT:
44FILE=
45foo
46FOO=
47## END
48## BUG bash STDOUT:
49FILE=
50FOO=
51## END
52
53#### Redirect in function body.
54fun() { echo hi; } 1>&2
55fun
56## STDOUT:
57## END
58## STDERR:
59hi
60## END
61
62#### Redirect in function body is evaluated multiple times
63i=0
64fun() { echo "file $i"; } 1> "$TMP/file$((i++))"
65fun
66fun
67echo i=$i
68echo __
69cat $TMP/file0
70echo __
71cat $TMP/file1
72## STDOUT:
73i=2
74__
75file 1
76__
77file 2
78## END
79## N-I dash stdout-json: ""
80## N-I dash status: 2
81
82#### Redirect in function body AND function call
83fun() { echo hi; } 1>&2
84fun 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
93echo one 1>&2
94echo two 1 >&2
95echo 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.
101echo two 1> $TMP/file-redir1.txt
102cat $TMP/file-redir1.txt
103## stdout: two
104
105#### Quoted filename redirect with spaces
106# POSIX makes node of this
107echo two \1 > $TMP/file-redir2.txt
108cat $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.
114echo one 1>&$TMP/nonexistent-filename__
115echo "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
122for i in $(seq 3)
123do
124 echo $i
125done > $TMP/redirect-for-loop.txt
126cat $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)
136do
137 echo $i
138done
139cat $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
147cat $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
152cat $TMP/block-stdout.txt | wc -c
153## stderr: foo
154## stdout: 10
155
156#### Redirect in the middle of two assignments
157FOO=foo >$TMP/out.txt BAR=bar printenv.py FOO BAR
158tac $TMP/out.txt
159## stdout-json: "bar\nfoo\n"
160
161#### Redirect in the middle of a command
162f=$TMP/out
163echo -n 1 2 '3 ' > $f
164echo -n 4 5 >> $f '6 '
165echo -n 7 >> $f 8 '9 '
166echo -n >> $f 1 2 '3 '
167echo >> $f -n 4 5 '6 '
168cat $f
169## stdout-json: "1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 "
170
171#### Named file descriptor
172exec {myfd}> $TMP/named-fd.txt
173echo named-fd-contents >& $myfd
174cat $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)
181exec 20> "$TMP/double-digit-fd.txt"
182echo hello20 >&20
183cat "$TMP/double-digit-fd.txt"
184## stdout: hello20
185## BUG dash stdout-json: ""
186## BUG dash status: 127
187
188#### : 9> fdleak (OSH regression)
189true 9> "$TMP/fd.txt"
190( echo world >&9 )
191cat "$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.
198case $SH in (mksh) exit ;; esac
199
200: 3>&3
201echo hello
202## stdout: hello
203## BUG mksh stdout-json: ""
204## BUG mksh status: 0
205
206#### : 3>&3-
207: 3>&3-
208echo 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)
215exec 3> "$TMP/fd.txt"
216echo hello 3>&- << EOF
217EOF
218echo world >&3
219exec 3>&- # close
220cat "$TMP/fd.txt"
221## STDOUT:
222hello
223world
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
230exec 3> "$TMP/fd3.txt"
231echo hello >&3
232echo world >&3
233exec 3>&- # close
234cat "$TMP/fd3.txt"
235## STDOUT:
236hello
237world
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
244exec 4> "$TMP/fd4.txt"
245echo hello >&4
246echo world >&4
247exec 4>&- # close
248cat "$TMP/fd4.txt"
249## STDOUT:
250hello
251world
252## END
253
254#### Redirect function stdout
255f() { echo one; echo two; }
256f > $TMP/redirect-func.txt
257cat $TMP/redirect-func.txt
258## stdout-json: "one\ntwo\n"
259
260#### Nested function stdout redirect
261# Shows that a stack is necessary.
262inner() {
263 echo i1
264 echo i2
265}
266outer() {
267 echo o1
268 inner > $TMP/inner.txt
269 echo o2
270}
271outer > $TMP/outer.txt
272cat $TMP/inner.txt
273echo --
274cat $TMP/outer.txt
275## stdout-json: "i1\ni2\n--\no1\no2\n"
276
277#### Redirect to empty string
278f=''
279echo s > "$f"
280echo "result=$?"
281set -o errexit
282echo s > "$f"
283echo 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
304opened=$(ls /proc/$$/fd)
305if echo "$opened" | egrep '^7$'; then
306 echo "FD 7 shouldn't be open"
307 echo "OPENED:"
308 echo "$opened"
309fi
310
311echo 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.
318exec 3>&1
319echo 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.
325exec 3>&1
326exec 4>&1
327echo three 1>&3
328echo four 1>&4
329## stdout-json: "three\nfour\n"
330## status: 0
331
332#### >| to clobber
333echo XX >| $TMP/c.txt
334
335set -o noclobber
336
337echo YY > $TMP/c.txt # not clobber
338echo status=$?
339
340cat $TMP/c.txt
341echo ZZ >| $TMP/c.txt
342
343cat $TMP/c.txt
344## STDOUT:
345status=1
346XX
347ZZ
348## END
349## OK dash STDOUT:
350status=2
351XX
352ZZ
353## END
354
355#### &> redirects stdout and stderr
356tmp="$(basename $SH)-$$.txt" # unique name for shell and test case
357#echo $tmp
358
359stdout_stderr.py &> $tmp
360
361# order is indeterminate
362grep STDOUT $tmp
363grep STDERR $tmp
364
365## STDOUT:
366STDOUT
367STDERR
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.
376case $SH in (dash|mksh) exit 1 ;; esac
377
378tmp="$(basename $SH)-$$.txt" # unique name for shell and test case
379
380stdout_stderr.py >&$tmp
381
382# order is indeterminate
383grep STDOUT $tmp
384grep STDERR $tmp
385
386## STDOUT:
387STDOUT
388STDERR
389## END
390## N-I dash/mksh status: 1
391## N-I dash/mksh stdout-json: ""
392
393#### 1>&- to close file descriptor
394exec 5> "$TMP/f.txt"
395echo hello >&5
396exec 5>&-
397echo world >&5
398cat "$TMP/f.txt"
399## stdout-json: "hello\n"
400
401#### 1>&2- to move file descriptor
402exec 5> "$TMP/f.txt"
403echo hello5 >&5
404exec 6>&5-
405echo world5 >&5
406echo world6 >&6
407exec 6>&-
408cat "$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.
420exec 6>&- 7>&-
421
422opened=$(ls /proc/$$/fd)
423if echo "$opened" | egrep '^7$'; then
424 echo "FD 7 shouldn't be open"
425 echo "OPENED:"
426 echo "$opened"
427fi
428if echo "$opened" | egrep '^6$'; then
429 echo "FD 6 shouldn't be open"
430 echo "OPENED:"
431 echo "$opened"
432fi
433
434exec 7> "$TMP/f.txt"
435: 6>&7 7>&-
436echo hello >&7
437: 6>&7-
438echo world >&7
439exec 7>&-
440cat "$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
451echo first >$TMP/rw.txt
452exec 8<>$TMP/rw.txt
453read line <&8
454echo line=$line
455echo second 1>&8
456echo CONTENTS
457cat $TMP/rw.txt
458## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
459
460#### <> for read/write named pipes
461rm -f "$TMP/f.pipe"
462mkfifo "$TMP/f.pipe"
463exec 8<> "$TMP/f.pipe"
464echo first >&8
465echo second >&8
466read line1 <&8
467read line2 <&8
468exec 8<&-
469echo 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.
476case $SH in
477 *dash)
478 exit 1
479 ;;
480esac
481
482echo "ok" > $TMP/f.txt
483stdout_stderr.py &>> $TMP/f.txt
484grep ok $TMP/f.txt >/dev/null && echo 'ok'
485grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
486grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
487## STDOUT:
488ok
489ok
490ok
491## END
492## N-I dash stdout-json: ""
493## N-I dash status: 1
494
495#### exec redirect then various builtins
496exec 5>$TMP/log.txt
497echo hi >&5
498set -o >&5
499echo done
500## STDOUT:
501done
502## END
503
504#### >$file touches a file
505rm -f myfile
506test -f myfile
507echo status=$?
508>myfile
509test -f myfile
510echo status=$?
511## STDOUT:
512status=1
513status=0
514## END
515# regression for OSH
516## stderr-json: ""
517
518#### $(< $file) yields the contents of the file
519
520echo FOO > myfile
521foo=$(< myfile)
522echo $foo
523## STDOUT:
524FOO
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?
532foo=$(echo begin; < myfile)
533echo $foo
534echo ---
535
536foo=$(< myfile; echo end)
537echo $foo
538echo ---
539
540foo=$(< myfile; <myfile)
541echo $foo
542echo ---
543
544## STDOUT:
545begin
546---
547end
548---
549
550---
551## END
552# weird, zsh behaves differently
553## OK zsh STDOUT:
554begin
555FOO
556---
557FOO
558end
559---
560FOO
561FOO
562---
563## END
564
565
566#### < file in pipeline and subshell doesn't work
567echo FOO > file2
568
569# This only happens in command subs, which is weird
570< file2 | tr A-Z a-z
571( < file2 )
572echo end
573## STDOUT:
574end
575## END
576
577#### 2>&1 with no command
578( exit 42 ) # status is reset after this
579echo status=$?
5802>&1
581echo status=$?
582## STDOUT:
583status=42
584status=0
585## END
586## stderr-json: ""
587
588#### 2&>1 (is it a redirect or is it like a&>1)
5892&>1
590echo status=$?
591## STDOUT:
592status=127
593## END
594## OK mksh/dash STDOUT:
595status=0
596## END
597
598#### can't mention big file descriptor
599echo hi 9>&1
600# trivia: 23 is the max descriptor for mksh
601#echo hi 24>&1
602echo hi 99>&1
603echo hi 100>&1
604## OK osh STDOUT:
605hi
606hi
607hi 100
608## END
609## STDOUT:
610hi
611hi 99
612hi 100
613## END
614## BUG bash STDOUT:
615hi
616hi
617hi
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> /
626echo 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.
636fd=100
637echo 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
644minfd=10
645case ${SH##*/} in
646(mksh) minfd=24 ;;
647(osh) minfd=100 ;;
648esac
649
650# 2. prepare first unused fd
651fd=$minfd
652is-fd-open() { : >&$1; }
653while 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
660done
661
662# 3. test
663echo 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.
670case $SH in (mksh|dash) exit 1 ;; esac
671# oil 0.8.pre4 fails to close fd by {fd}&-.
672exec {fd}>file1
673echo foo >&$fd
674exec {fd}>&-
675echo bar >&$fd
676cat file1
677## stdout: foo
678## N-I mksh/dash stdout-json: ""
679## N-I mksh/dash status: 1