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 #### Redirect function stdout
185 f() { echo one; echo two; }
186 f > $TMP/redirect-func.txt
187 cat $TMP/redirect-func.txt
188 ## stdout-json: "one\ntwo\n"
189
190 #### Nested function stdout redirect
191 # Shows that a stack is necessary.
192 inner() {
193 echo i1
194 echo i2
195 }
196 outer() {
197 echo o1
198 inner > $TMP/inner.txt
199 echo o2
200 }
201 outer > $TMP/outer.txt
202 cat $TMP/inner.txt
203 echo --
204 cat $TMP/outer.txt
205 ## stdout-json: "i1\ni2\n--\no1\no2\n"
206
207 #### Redirect to empty string
208 f=''
209 echo s > "$f"
210 echo "result=$?"
211 set -o errexit
212 echo s > "$f"
213 echo DONE
214 ## stdout: result=1
215 ## status: 1
216 ## OK dash stdout: result=2
217 ## OK dash status: 2
218
219 #### Redirect to file descriptor that's not open
220 # BUGS:
221 # - dash doesn't allow file descriptors greater than 9. (This is a good thing,
222 # because the bash chapter in AOSA book mentions that juggling user vs. system
223 # file descriptors is a huge pain.)
224 # - But somehow running in parallel under spec-runner.sh changes whether descriptor
225 # 3 is open. e.g. 'echo hi 1>&3'. Possibly because of /usr/bin/time. The
226 # _tmp/spec/*.task.txt file gets corrupted!
227 # - Oh this is because I use time --output-file. That opens descriptor 3. And
228 # then time forks the shell script. The file descriptor table is inherited.
229 # - You actually have to set the file descriptor to something. What do
230 # configure and debootstrap too?
231 echo hi 1>&9
232 ## status: 1
233 ## OK dash status: 2
234
235 #### Open descriptor with exec
236 # What is the point of this? ./configure scripts and debootstrap use it.
237 exec 3>&1
238 echo hi 1>&3
239 ## stdout: hi
240 ## status: 0
241
242 #### Open multiple descriptors with exec
243 # What is the point of this? ./configure scripts and debootstrap use it.
244 exec 3>&1
245 exec 4>&1
246 echo three 1>&3
247 echo four 1>&4
248 ## stdout-json: "three\nfour\n"
249 ## status: 0
250
251 #### >| to clobber
252 echo XX >| $TMP/c.txt
253 set -o noclobber
254 echo YY > $TMP/c.txt # not globber
255 echo status=$?
256 cat $TMP/c.txt
257 echo ZZ >| $TMP/c.txt
258 cat $TMP/c.txt
259 ## stdout-json: "status=1\nXX\nZZ\n"
260 ## OK dash stdout-json: "status=2\nXX\nZZ\n"
261
262 #### &> redirects stdout and stderr
263 stdout_stderr.py &> $TMP/f.txt
264 # order is indeterminate
265 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
266 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
267 ## STDOUT:
268 ok
269 ok
270 ## END
271 ## N-I dash stdout: STDOUT
272 ## N-I dash stderr: STDERR
273 ## N-I dash status: 1
274
275 #### 1>&2- to close file descriptor
276 # NOTE: "hi\n" goes to stderr, but it's hard to test this because other shells
277 # put errors on stderr.
278 echo hi 1>&2-
279 ## stdout-json: ""
280 ## N-I dash status: 2
281 ## N-I dash stdout-json: ""
282 ## N-I mksh status: 1
283 ## N-I mksh stdout-json: ""
284
285 #### <> for read/write
286 echo first >$TMP/rw.txt
287 exec 8<>$TMP/rw.txt
288 read line <&8
289 echo line=$line
290 echo second 1>&8
291 echo CONTENTS
292 cat $TMP/rw.txt
293 ## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
294
295 #### &>> appends stdout and stderr
296
297 # Fix for flaky tests: dash behaves non-deterministically under load! It
298 # doesn't implement the behavior anyway so I don't care why.
299 case $SH in
300 *dash)
301 exit 1
302 ;;
303 esac
304
305 echo "ok" > $TMP/f.txt
306 stdout_stderr.py &>> $TMP/f.txt
307 grep ok $TMP/f.txt >/dev/null && echo 'ok'
308 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
309 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
310 ## STDOUT:
311 ok
312 ok
313 ok
314 ## END
315 ## N-I dash stdout-json: ""
316 ## N-I dash status: 1
317
318 #### exec redirect then various builtins
319 exec 5>$TMP/log.txt
320 echo hi >&5
321 set -o >&5
322 echo done
323 ## STDOUT:
324 done
325 ## END
326
327 #### >$file touches a file
328 cd $TMP
329 rm -f myfile
330 test -f myfile
331 echo status=$?
332 >myfile
333 test -f myfile
334 echo status=$?
335 ## STDOUT:
336 status=1
337 status=0
338 ## END
339 # regression for OSH
340 ## stderr-json: ""
341
342 #### $(< $file) yields the contents of the file
343 # note that it doesn't do this without a command sub!
344 cd $TMP
345 echo FOO > myfile
346 foo=$(< myfile)
347 echo $foo
348 ## STDOUT:
349 FOO
350 ## END
351 ## N-I dash stdout:
352
353 #### 2>&1 with no command
354 cd $TMP
355 ( exit 42 ) # status is reset after this
356 echo status=$?
357 2>&1
358 echo status=$?
359 ## STDOUT:
360 status=42
361 status=0
362 ## END
363 ## stderr-json: ""
364
365 #### 2&>1 (is it a redirect or is it like a&>1)
366 cd $TMP
367 2&>1
368 echo status=$?
369 ## STDOUT:
370 status=127
371 ## END
372 ## OK mksh/dash STDOUT:
373 status=0
374 ## END