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 5< $TMP/lessamp.txt
11 read line <&5
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 ### No command
27 # Hm this is valid in bash and dash. It's parsed as an assigment with a
28 # redirect, which doesn't make sense. But it's a mistake, and should be a W2
29 # warning for us.
30 FOO=bar 2>/dev/null
31
32 ### Redirect in subshell
33 FOO=$(echo foo 1>&2)
34 echo $FOO
35 # stdout:
36 # stderr: foo
37
38 ### Redirect in assignment
39 # dash captures stderr to a file here, which seems correct. Bash doesn't and
40 # just lets it go to actual stderr.
41 # For now we agree with dash/mksh, since it involves fewer special cases in the
42 # code.
43 FOO=$(echo foo 1>&2) 2>$TMP/no-command.txt
44 echo FILE=
45 cat $TMP/no-command.txt
46 echo "FOO=$FOO"
47 # stdout-json: "FILE=\nfoo\nFOO=\n"
48 # BUG bash stdout-json: "FILE=\nFOO=\n"
49
50 ### Redirect in function body.
51 func() { echo hi; } 1>&2
52 func
53 # stdout-json: ""
54 # stderr-json: "hi\n"
55
56 ### Redirect in function body is evaluated multiple times
57 i=0
58 func() { echo "file $i"; } 1> "$TMP/file$((i++))"
59 func
60 func
61 echo i=$i
62 echo __
63 cat $TMP/file0
64 echo __
65 cat $TMP/file1
66 # stdout-json: "i=2\n__\nfile 1\n__\nfile 2\n"
67 # N-I dash stdout-json: ""
68 # N-I dash status: 2
69
70 ### Redirect in function body AND function call
71 func() { echo hi; } 1>&2
72 func 2>&1
73 # stdout-json: "hi\n"
74 # stderr-json: ""
75
76 ### Descriptor redirect with spaces
77 # Hm this seems like a failure of lookahead! The second thing should look to a
78 # file-like thing.
79 # I think this is a posix issue.
80 # tag: posix-issue
81 echo one 1>&2
82 echo two 1 >&2
83 echo three 1>& 2
84 # stderr-json: "one\ntwo 1\nthree\n"
85
86 ### Filename redirect with spaces
87 # This time 1 *is* a descriptor, not a word. If you add a space between 1 and
88 # >, it doesn't work.
89 echo two 1> $TMP/file-redir1.txt
90 cat $TMP/file-redir1.txt
91 # stdout: two
92
93 ### Quoted filename redirect with spaces
94 # POSIX makes node of this
95 echo two \1 > $TMP/file-redir2.txt
96 cat $TMP/file-redir2.txt
97 # stdout: two 1
98
99 ### Descriptor redirect with filename
100 # bash/mksh treat this like a filename, not a descriptor.
101 # dash aborts.
102 echo one 1>&$TMP/nonexistent-filename__
103 echo "status=$?"
104 # stdout: status=1
105 # BUG bash stdout: status=0
106 # OK dash stdout-json: ""
107 # OK dash status: 2
108
109 ### redirect for loop
110 for i in $(seq 3)
111 do
112 echo $i
113 done > $TMP/redirect-for-loop.txt
114 cat $TMP/redirect-for-loop.txt
115 # stdout-json: "1\n2\n3\n"
116
117 ### redirect subshell
118 ( echo foo ) 1>&2
119 # stderr: foo
120 # stdout-json: ""
121
122 ### Prefix redirect for loop -- not allowed
123 >$TMP/redirect2.txt for i in $(seq 3)
124 do
125 echo $i
126 done
127 cat $TMP/redirect2.txt
128 # status: 2
129 # OK mksh status: 1
130
131 ### Brace group redirect
132 # Suffix works, but prefix does NOT work.
133 # That comes from '| compound_command redirect_list' in the grammar!
134 { echo block-redirect; } > $TMP/br.txt
135 cat $TMP/br.txt | wc -c
136 # stdout: 15
137
138 ### Redirect echo to stderr, and then redirect all of stdout somewhere.
139 { echo foo 1>&2; echo 012345789; } > $TMP/block-stdout.txt
140 cat $TMP/block-stdout.txt | wc -c
141 # stderr: foo
142 # stdout: 10
143
144 ### Redirect in the middle of two assignments
145 FOO=foo >$TMP/out.txt BAR=bar printenv.py FOO BAR
146 tac $TMP/out.txt
147 # stdout-json: "bar\nfoo\n"
148
149 ### Redirect in the middle of a command
150 f=$TMP/out
151 echo -n 1 2 '3 ' > $f
152 echo -n 4 5 >> $f '6 '
153 echo -n 7 >> $f 8 '9 '
154 echo -n >> $f 1 2 '3 '
155 echo >> $f -n 4 5 '6 '
156 cat $f
157 # stdout-json: "1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 "
158
159 ### Named file descriptor
160 exec {myfd}> $TMP/named-fd.txt
161 echo named-fd-contents >& $myfd
162 cat $TMP/named-fd.txt
163 # stdout: named-fd-contents
164 # status: 0
165 # N-I dash/mksh stdout-json: ""
166 # N-I dash/mksh status: 127
167
168 ### Redirect function stdout
169 f() { echo one; echo two; }
170 f > $TMP/redirect-func.txt
171 cat $TMP/redirect-func.txt
172 # stdout-json: "one\ntwo\n"
173
174 ### Nested function stdout redirect
175 # Shows that a stack is necessary.
176 inner() {
177 echo i1
178 echo i2
179 }
180 outer() {
181 echo o1
182 inner > $TMP/inner.txt
183 echo o2
184 }
185 outer > $TMP/outer.txt
186 cat $TMP/inner.txt
187 echo --
188 cat $TMP/outer.txt
189 # stdout-json: "i1\ni2\n--\no1\no2\n"
190
191 ### Redirect to empty string
192 f=''
193 echo s > "$f"
194 echo "result=$?"
195 set -o errexit
196 echo s > "$f"
197 echo DONE
198 # stdout: result=1
199 # status: 1
200 # OK dash stdout: result=2
201 # OK dash status: 2
202
203 ### Redirect to file descriptor that's not open
204 # BUGS:
205 # - dash doesn't allow file descriptors greater than 9. (This is a good thing,
206 # because the bash chapter in AOSA book mentions that juggling user vs. system
207 # file descriptors is a huge pain.)
208 # - But somehow running in parallel under spec-runner.sh changes whether descriptor
209 # 3 is open. e.g. 'echo hi 1>&3'. Possibly because of /usr/bin/time. The
210 # _tmp/spec/*.task.txt file gets corrupted!
211 # - Oh this is because I use time --output-file. That opens descriptor 3. And
212 # then time forks the shell script. The file descriptor table is inherited.
213 # - You actually have to set the file descriptor to something. What do
214 # configure and debootstrap too?
215 echo hi 1>&9
216 # status: 1
217 # OK dash status: 2
218
219 ### Open descriptor with exec
220 # What is the point of this? ./configure scripts and debootstrap use it.
221 exec 3>&1
222 echo hi 1>&3
223 # stdout: hi
224 # status: 0
225
226 ### Open multiple descriptors with exec
227 # What is the point of this? ./configure scripts and debootstrap use it.
228 exec 3>&1
229 exec 4>&1
230 echo three 1>&3
231 echo four 1>&4
232 # stdout-json: "three\nfour\n"
233 # status: 0
234
235 ### >| to clobber
236 echo XX >| $TMP/c.txt
237 set -o noclobber
238 echo YY > $TMP/c.txt # not globber
239 echo status=$?
240 cat $TMP/c.txt
241 echo ZZ >| $TMP/c.txt
242 cat $TMP/c.txt
243 # stdout-json: "status=1\nXX\nZZ\n"
244 # OK dash stdout-json: "status=2\nXX\nZZ\n"
245
246 ### &> redirects stdout and stderr
247 stdout_stderr.py &> $TMP/f.txt
248 # order is indeterminate
249 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
250 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
251 # stdout-json: "ok\nok\n"
252 # N-I dash stdout: STDOUT
253 # N-I dash stderr: STDERR
254 # N-I dash status: 1
255
256 ### 1>&2- to close file descriptor
257 # NOTE: "hi\n" goes to stderr, but it's hard to test this because other shells
258 # put errors on stderr.
259 echo hi 1>&2-
260 # stdout-json: ""
261 # N-I dash status: 2
262 # N-I dash stdout-json: ""
263 # N-I mksh status: 1
264 # N-I mksh stdout-json: ""
265
266 ### <> for read/write
267 echo first >$TMP/rw.txt
268 exec 8<>$TMP/rw.txt
269 read line <&8
270 echo line=$line
271 echo second 1>&8
272 echo CONTENTS
273 cat $TMP/rw.txt
274 # stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"