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