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 is invalid
33 # Hm this is valid in bash and dash. It's parsed as an assigment with a
34 # redirect, which doesn't make sense. But it's a mistake, and should be a W2
35 # warning for us.
36 FOO=bar 2>/dev/null
37 ## status: 2
38 ## OK bash/dash/mksh status: 0
39
40 #### Redirect in assignment
41 # dash captures stderr to a file here, which seems correct. Bash doesn't and
42 # just lets it go to actual stderr.
43 # For now we agree with dash/mksh, since it involves fewer special cases in the
44 # code.
45 FOO=$(echo foo 1>&2) 2>$TMP/no-command.txt
46 echo FILE=
47 cat $TMP/no-command.txt
48 echo "FOO=$FOO"
49 ## status: 2
50 ## OK dash/mksh stdout-json: "FILE=\nfoo\nFOO=\n"
51 ## OK dash/mksh status: 0
52 ## BUG bash stdout-json: "FILE=\nFOO=\n"
53 ## OK bash status: 0
54
55 #### Redirect in function body.
56 func() { echo hi; } 1>&2
57 func
58 ## stdout-json: ""
59 ## stderr-json: "hi\n"
60
61 #### Bad redirects in function body
62 empty=''
63 func() { echo hi; } > $empty
64 func
65 echo status=$?
66 ## stdout: status=1
67 ## OK dash stdout: status=2
68
69 #### Redirect in function body is evaluated multiple times
70 i=0
71 func() { echo "file $i"; } 1> "$TMP/file$((i++))"
72 func
73 func
74 echo i=$i
75 echo __
76 cat $TMP/file0
77 echo __
78 cat $TMP/file1
79 ## stdout-json: "i=2\n__\nfile 1\n__\nfile 2\n"
80 ## N-I dash stdout-json: ""
81 ## N-I dash status: 2
82
83 #### Redirect in function body AND function call
84 func() { echo hi; } 1>&2
85 func 2>&1
86 ## stdout-json: "hi\n"
87 ## stderr-json: ""
88
89 #### Descriptor redirect with spaces
90 # Hm this seems like a failure of lookahead! The second thing should look to a
91 # file-like thing.
92 # I think this is a posix issue.
93 # tag: posix-issue
94 echo one 1>&2
95 echo two 1 >&2
96 echo three 1>& 2
97 ## stderr-json: "one\ntwo 1\nthree\n"
98
99 #### Filename redirect with spaces
100 # This time 1 *is* a descriptor, not a word. If you add a space between 1 and
101 # >, it doesn't work.
102 echo two 1> $TMP/file-redir1.txt
103 cat $TMP/file-redir1.txt
104 ## stdout: two
105
106 #### Quoted filename redirect with spaces
107 # POSIX makes node of this
108 echo two \1 > $TMP/file-redir2.txt
109 cat $TMP/file-redir2.txt
110 ## stdout: two 1
111
112 #### Descriptor redirect with filename
113 # bash/mksh treat this like a filename, not a descriptor.
114 # dash aborts.
115 echo one 1>&$TMP/nonexistent-filename__
116 echo "status=$?"
117 ## stdout: status=1
118 ## BUG bash stdout: status=0
119 ## OK dash stdout-json: ""
120 ## OK dash status: 2
121
122 #### redirect for loop
123 for i in $(seq 3)
124 do
125 echo $i
126 done > $TMP/redirect-for-loop.txt
127 cat $TMP/redirect-for-loop.txt
128 ## stdout-json: "1\n2\n3\n"
129
130 #### redirect subshell
131 ( echo foo ) 1>&2
132 ## stderr: foo
133 ## stdout-json: ""
134
135 #### Prefix redirect for loop -- not allowed
136 >$TMP/redirect2.txt for i in $(seq 3)
137 do
138 echo $i
139 done
140 cat $TMP/redirect2.txt
141 ## status: 2
142 ## OK mksh status: 1
143
144 #### Brace group redirect
145 # Suffix works, but prefix does NOT work.
146 # That comes from '| compound_command redirect_list' in the grammar!
147 { echo block-redirect; } > $TMP/br.txt
148 cat $TMP/br.txt | wc -c
149 ## stdout: 15
150
151 #### Redirect echo to stderr, and then redirect all of stdout somewhere.
152 { echo foo 1>&2; echo 012345789; } > $TMP/block-stdout.txt
153 cat $TMP/block-stdout.txt | wc -c
154 ## stderr: foo
155 ## stdout: 10
156
157 #### Redirect in the middle of two assignments
158 FOO=foo >$TMP/out.txt BAR=bar printenv.py FOO BAR
159 tac $TMP/out.txt
160 ## stdout-json: "bar\nfoo\n"
161
162 #### Redirect in the middle of a command
163 f=$TMP/out
164 echo -n 1 2 '3 ' > $f
165 echo -n 4 5 >> $f '6 '
166 echo -n 7 >> $f 8 '9 '
167 echo -n >> $f 1 2 '3 '
168 echo >> $f -n 4 5 '6 '
169 cat $f
170 ## stdout-json: "1 2 3 4 5 6 7 8 9 1 2 3 4 5 6 "
171
172 #### Named file descriptor
173 exec {myfd}> $TMP/named-fd.txt
174 echo named-fd-contents >& $myfd
175 cat $TMP/named-fd.txt
176 ## stdout: named-fd-contents
177 ## status: 0
178 ## N-I dash/mksh stdout-json: ""
179 ## N-I dash/mksh status: 127
180
181 #### Redirect function stdout
182 f() { echo one; echo two; }
183 f > $TMP/redirect-func.txt
184 cat $TMP/redirect-func.txt
185 ## stdout-json: "one\ntwo\n"
186
187 #### Nested function stdout redirect
188 # Shows that a stack is necessary.
189 inner() {
190 echo i1
191 echo i2
192 }
193 outer() {
194 echo o1
195 inner > $TMP/inner.txt
196 echo o2
197 }
198 outer > $TMP/outer.txt
199 cat $TMP/inner.txt
200 echo --
201 cat $TMP/outer.txt
202 ## stdout-json: "i1\ni2\n--\no1\no2\n"
203
204 #### Redirect to empty string
205 f=''
206 echo s > "$f"
207 echo "result=$?"
208 set -o errexit
209 echo s > "$f"
210 echo DONE
211 ## stdout: result=1
212 ## status: 1
213 ## OK dash stdout: result=2
214 ## OK dash status: 2
215
216 #### Redirect to file descriptor that's not open
217 # BUGS:
218 # - dash doesn't allow file descriptors greater than 9. (This is a good thing,
219 # because the bash chapter in AOSA book mentions that juggling user vs. system
220 # file descriptors is a huge pain.)
221 # - But somehow running in parallel under spec-runner.sh changes whether descriptor
222 # 3 is open. e.g. 'echo hi 1>&3'. Possibly because of /usr/bin/time. The
223 # _tmp/spec/*.task.txt file gets corrupted!
224 # - Oh this is because I use time --output-file. That opens descriptor 3. And
225 # then time forks the shell script. The file descriptor table is inherited.
226 # - You actually have to set the file descriptor to something. What do
227 # configure and debootstrap too?
228 echo hi 1>&9
229 ## status: 1
230 ## OK dash status: 2
231
232 #### Open descriptor with exec
233 # What is the point of this? ./configure scripts and debootstrap use it.
234 exec 3>&1
235 echo hi 1>&3
236 ## stdout: hi
237 ## status: 0
238
239 #### Open multiple descriptors with exec
240 # What is the point of this? ./configure scripts and debootstrap use it.
241 exec 3>&1
242 exec 4>&1
243 echo three 1>&3
244 echo four 1>&4
245 ## stdout-json: "three\nfour\n"
246 ## status: 0
247
248 #### >| to clobber
249 echo XX >| $TMP/c.txt
250 set -o noclobber
251 echo YY > $TMP/c.txt # not globber
252 echo status=$?
253 cat $TMP/c.txt
254 echo ZZ >| $TMP/c.txt
255 cat $TMP/c.txt
256 ## stdout-json: "status=1\nXX\nZZ\n"
257 ## OK dash stdout-json: "status=2\nXX\nZZ\n"
258
259 #### &> redirects stdout and stderr
260 stdout_stderr.py &> $TMP/f.txt
261 # order is indeterminate
262 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
263 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
264 ## STDOUT:
265 ok
266 ok
267 ## END
268 ## N-I dash stdout: STDOUT
269 ## N-I dash stderr: STDERR
270 ## N-I dash status: 1
271
272 #### 1>&2- to close file descriptor
273 # NOTE: "hi\n" goes to stderr, but it's hard to test this because other shells
274 # put errors on stderr.
275 echo hi 1>&2-
276 ## stdout-json: ""
277 ## N-I dash status: 2
278 ## N-I dash stdout-json: ""
279 ## N-I mksh status: 1
280 ## N-I mksh stdout-json: ""
281
282 #### <> for read/write
283 echo first >$TMP/rw.txt
284 exec 8<>$TMP/rw.txt
285 read line <&8
286 echo line=$line
287 echo second 1>&8
288 echo CONTENTS
289 cat $TMP/rw.txt
290 ## stdout-json: "line=first\nCONTENTS\nfirst\nsecond\n"
291
292 #### &>> appends stdout and stderr
293
294 # Fix for flaky tests: dash behaves non-deterministically under load! It
295 # doesn't implement the behavior anyway so I don't care why.
296 case $SH in
297 *dash)
298 exit 1
299 ;;
300 esac
301
302 echo "ok" > $TMP/f.txt
303 stdout_stderr.py &>> $TMP/f.txt
304 grep ok $TMP/f.txt >/dev/null && echo 'ok'
305 grep STDOUT $TMP/f.txt >/dev/null && echo 'ok'
306 grep STDERR $TMP/f.txt >/dev/null && echo 'ok'
307 ## STDOUT:
308 ok
309 ok
310 ok
311 ## END
312 ## N-I dash stdout-json: ""
313 ## N-I dash status: 1
314
315 #### exec redirect then various builtins
316 exec 5>$TMP/log.txt
317 echo hi >&5
318 set -o >&5
319 echo done
320 ## STDOUT:
321 done
322 ## END
323
324 #### >$file touches a file
325 cd $TMP
326 rm -f myfile
327 test -f myfile
328 echo status=$?
329 >myfile
330 test -f myfile
331 echo status=$?
332 ## STDOUT:
333 status=1
334 status=0
335 ## END
336 # regression for OSH
337 ## stderr-json: ""
338
339 #### $(< $file) yields the contents of the file
340 # note that it doesn't do this without a command sub!
341 cd $TMP
342 echo FOO > myfile
343 foo=$(< myfile)
344 echo $foo
345 ## STDOUT:
346 FOO
347 ## END
348 ## N-I dash STDOUT:
349 ## END
350
351 #### 2>&1 with no command
352 cd $TMP
353 ( exit 42 ) # status is reset after this
354 echo status=$?
355 2>&1
356 echo status=$?
357 ## STDOUT:
358 status=42
359 status=0
360 ## END
361 ## stderr-json: ""
362
363 #### 2&>1 (is it a redirect or is it like a&>1)
364 cd $TMP
365 2&>1
366 echo status=$?
367 ## STDOUT:
368 status=127
369 ## END
370 ## OK mksh/dash STDOUT:
371 status=0
372 ## END