1 #!/bin/bash
2 #
3 # echo, read
4 # later: perhaps mapfile, etc.
5
6 #### echo dashes
7 echo -
8 echo --
9 echo ---
10 ## stdout-json: "-\n--\n---\n"
11 ## BUG zsh stdout-json: "\n--\n---\n"
12
13 #### echo backslashes
14 echo \\
15 echo '\'
16 echo '\\'
17 echo "\\"
18 ## STDOUT:
19 \
20 \
21 \\
22 \
23 ## BUG dash/mksh/zsh STDOUT:
24 \
25 \
26 \
27 \
28 ## END
29
30 #### echo -e backslashes
31 echo -e \\
32 echo -e '\'
33 echo -e '\\'
34 echo -e "\\"
35 ## STDOUT:
36 \
37 \
38 \
39 \
40 ## N-I dash STDOUT:
41 -e \
42 -e \
43 -e \
44 -e \
45 ## END
46
47 #### echo -en
48 echo -en 'abc\ndef\n'
49 ## stdout-json: "abc\ndef\n"
50 ## N-I dash stdout-json: "-en abc\ndef\n\n"
51
52 #### echo -ez (invalid flag)
53 # bash differs from the other three shells, but its behavior is possibly more
54 # sensible, if you're going to ignore the error. It doesn't make sense for
55 # the 'e' to mean 2 different things simultaneously: flag and literal to be
56 # printed.
57 echo -ez 'abc\n'
58 ## stdout-json: "-ez abc\\n\n"
59 ## OK dash/mksh/zsh stdout-json: "-ez abc\n\n"
60
61 #### echo -e with embedded newline
62 flags='-e'
63 case $SH in */dash) flags='' ;; esac
64
65 echo $flags 'foo
66 bar'
67 ## STDOUT:
68 foo
69 bar
70 ## END
71
72 #### echo -e line continuation
73 flags='-e'
74 case $SH in */dash) flags='' ;; esac
75
76 echo $flags 'foo\
77 bar'
78 ## STDOUT:
79 foo\
80 bar
81 ## END
82
83 #### echo -e with C escapes
84 # https://www.gnu.org/software/bash/manual/bashref.html#Bourne-Shell-Builtins
85 # not sure why \c is like NUL?
86 # zsh doesn't allow \E for some reason.
87 echo -e '\a\b\d\e\f'
88 ## stdout-json: "\u0007\u0008\\d\u001b\u000c\n"
89 ## N-I dash stdout-json: "-e \u0007\u0008\\d\\e\u000c\n"
90
91 #### echo -e with whitespace C escapes
92 echo -e '\n\r\t\v'
93 ## stdout-json: "\n\r\t\u000b\n"
94 ## N-I dash stdout-json: "-e \n\r\t\u000b\n"
95
96 #### \0
97 echo -e 'ab\0cd'
98 ## stdout-json: "ab\u0000cd\n"
99 # dash truncates it
100 ## BUG dash stdout-json: "-e ab\n"
101
102 #### \c stops processing input
103 flags='-e'
104 case $SH in */dash) flags='' ;; esac
105
106 echo $flags xy 'ab\cde' 'ab\cde'
107 ## stdout-json: "xy ab"
108 ## N-I mksh stdout-json: "xy abde abde"
109
110 #### echo -e with hex escape
111 echo -e 'abcd\x65f'
112 ## stdout-json: "abcdef\n"
113 ## N-I dash stdout-json: "-e abcd\\x65f\n"
114
115 #### echo -e with octal escape
116 flags='-e'
117 case $SH in */dash) flags='' ;; esac
118
119 echo $flags 'abcd\044e'
120 ## stdout-json: "abcd$e\n"
121
122 #### echo -e with 4 digit unicode escape
123 flags='-e'
124 case $SH in */dash) flags='' ;; esac
125
126 echo $flags 'abcd\u0065f'
127 ## STDOUT:
128 abcdef
129 ## END
130 ## N-I dash/ash stdout-json: "abcd\\u0065f\n"
131
132 #### echo -e with 8 digit unicode escape
133 flags='-e'
134 case $SH in */dash) flags='' ;; esac
135
136 echo $flags 'abcd\U00000065f'
137 ## STDOUT:
138 abcdef
139 ## END
140 ## N-I dash/ash stdout-json: "abcd\\U00000065f\n"
141
142 #### \0377 is the highest octal byte
143 echo -en '\03777' | od -A n -t x1 | sed 's/ \+/ /g'
144 ## stdout-json: " ff 37\n"
145 ## N-I dash stdout-json: " 2d 65 6e 20 ff 37 0a\n"
146
147 #### \0400 is one more than the highest octal byte
148 # It is 256 % 256 which gets interpreted as a NUL byte.
149 echo -en '\04000' | od -A n -t x1 | sed 's/ \+/ /g'
150 ## stdout-json: " 00 30\n"
151 ## BUG ash stdout-json: " 20 30 30\n"
152 ## N-I dash stdout-json: " 2d 65 6e 20\n"
153
154 #### \0777 is out of range
155 flags='-en'
156 case $SH in */dash) flags='-n' ;; esac
157
158 echo $flags '\0777' | od -A n -t x1 | sed 's/ \+/ /g'
159 ## stdout-json: " ff\n"
160 ## BUG mksh stdout-json: " c3 bf\n"
161 ## BUG ash stdout-json: " 3f 37\n"
162
163 #### incomplete hex escape
164 echo -en 'abcd\x6' | od -A n -c | sed 's/ \+/ /g'
165 ## stdout-json: " a b c d 006\n"
166 ## N-I dash stdout-json: " - e n a b c d \\ x 6 \\n\n"
167
168 #### \x
169 # I consider mksh and zsh a bug because \x is not an escape
170 echo -e '\x' '\xg' | od -A n -c | sed 's/ \+/ /g'
171 ## stdout-json: " \\ x \\ x g \\n\n"
172 ## N-I dash stdout-json: " - e \\ x \\ x g \\n\n"
173 ## BUG mksh/zsh stdout-json: " \\0 \\0 g \\n\n"
174
175 #### incomplete octal escape
176 flags='-en'
177 case $SH in */dash) flags='-n' ;; esac
178
179 echo $flags 'abcd\04' | od -A n -c | sed 's/ \+/ /g'
180 ## stdout-json: " a b c d 004\n"
181
182 #### incomplete unicode escape
183 echo -en 'abcd\u006' | od -A n -c | sed 's/ \+/ /g'
184 ## stdout-json: " a b c d 006\n"
185 ## N-I dash stdout-json: " - e n a b c d \\ u 0 0 6 \\n\n"
186 ## BUG ash stdout-json: " a b c d \\ u 0 0 6\n"
187
188 #### \u6
189 flags='-en'
190 case $SH in */dash) flags='-n' ;; esac
191
192 echo $flags '\u6' | od -A n -c | sed 's/ \+/ /g'
193 ## stdout-json: " 006\n"
194 ## N-I dash/ash stdout-json: " \\ u 6\n"
195
196 #### \0 \1 \8
197 # \0 is special, but \1 isn't in bash
198 # \1 is special in dash! geez
199 flags='-en'
200 case $SH in */dash) flags='-n' ;; esac
201
202 echo $flags '\0' '\1' '\8' | od -A n -c | sed 's/ \+/ /g'
203 ## stdout-json: " \\0 \\ 1 \\ 8\n"
204 ## BUG dash stdout-json: " 001 \\ 8\n"
205 ## BUG ash stdout-json: " \\0 001 \\ 8\n"
206
207 #### Read builtin
208 # NOTE: there are TABS below
209 read x <<EOF
210 A B C D E
211 FG
212 EOF
213 echo "[$x]"
214 ## stdout: [A B C D E]
215 ## status: 0
216
217 #### Read from empty file
218 echo -n '' > $TMP/empty.txt
219 read x < $TMP/empty.txt
220 argv.py "status=$?" "$x"
221 ## stdout: ['status=1', '']
222 ## status: 0
223
224 #### Read builtin with no newline.
225 # This is odd because the variable is populated successfully. OSH/Oil might
226 # need a separate put reading feature that doesn't use IFS.
227 echo -n ZZZ | { read x; echo $?; echo $x; }
228 ## stdout-json: "1\nZZZ\n"
229 ## status: 0
230
231 #### Read builtin with multiple variables
232 # NOTE: there are TABS below
233 read x y z <<EOF
234 A B C D E
235 FG
236 EOF
237 echo "[$x/$y/$z]"
238 ## stdout: [A/B/C D E]
239 ## BUG dash stdout: [A/B/C D E ]
240 ## status: 0
241
242 #### Read builtin with not enough variables
243 set -o errexit
244 set -o nounset # hm this doesn't change it
245 read x y z <<EOF
246 A B
247 EOF
248 echo /$x/$y/$z/
249 ## stdout: /A/B//
250 ## status: 0
251
252 #### Read -n (with $REPLY)
253 echo 12345 > $TMP/readn.txt
254 read -n 4 x < $TMP/readn.txt
255 read -n 2 < $TMP/readn.txt # Do it again with no variable
256 argv.py $x $REPLY
257 ## stdout: ['1234', '12']
258 ## N-I dash/zsh stdout: []
259
260 #### Read uses $REPLY (without -n)
261 echo 123 > $TMP/readreply.txt
262 read < $TMP/readreply.txt
263 echo $REPLY
264 ## stdout: 123
265 ## N-I dash stdout:
266
267 #### read -r ignores backslashes
268 echo 'one\ two' > $TMP/readr.txt
269 read escaped < $TMP/readr.txt
270 read -r raw < $TMP/readr.txt
271 argv.py "$escaped" "$raw"
272 ## stdout: ['one two', 'one\\ two']
273
274 #### read -r with other backslash escapes
275 echo 'one\ two\x65three' > $TMP/readr.txt
276 read escaped < $TMP/readr.txt
277 read -r raw < $TMP/readr.txt
278 argv.py "$escaped" "$raw"
279 # mksh respects the hex escapes here, but other shells don't!
280 ## stdout: ['one twox65three', 'one\\ two\\x65three']
281 ## BUG mksh/zsh stdout: ['one twoethree', 'one\\ twoethree']
282
283 #### read with line continuation reads multiple physical lines
284 # NOTE: osh failing because of file descriptor issue. stdin has to be closed!
285 tmp=$TMP/$(basename $SH)-readr.txt
286 echo -e 'one\\\ntwo\n' > $tmp
287 read escaped < $tmp
288 read -r raw < $tmp
289 argv.py "$escaped" "$raw"
290 ## stdout: ['onetwo', 'one\\']
291 ## N-I dash stdout: ['-e onetwo', '-e one\\']
292
293 #### read multiple vars spanning many lines
294 read x y << 'EOF'
295 one-\
296 two three-\
297 four five-\
298 six
299 EOF
300 argv.py "$x" "$y" "$z"
301 ## stdout: ['one-two', 'three-four five-six', '']
302
303 #### read -r with \n
304 echo '\nline' > $TMP/readr.txt
305 read escaped < $TMP/readr.txt
306 read -r raw < $TMP/readr.txt
307 argv.py "$escaped" "$raw"
308 # dash/mksh/zsh are bugs because at least the raw mode should let you read a
309 # literal \n.
310 ## stdout: ['nline', '\\nline']
311 ## BUG dash/mksh/zsh stdout: ['', '']
312
313 #### Read with IFS=$'\n'
314 # The leading spaces are stripped if they appear in IFS.
315 IFS=$(echo -e '\n')
316 read var <<EOF
317 a b c
318 d e f
319 EOF
320 echo "[$var]"
321 ## stdout: [ a b c]
322 ## N-I dash stdout: [a b c]
323
324 #### Read multiple lines with IFS=:
325 # The leading spaces are stripped if they appear in IFS.
326 # IFS chars are escaped with :.
327 tmp=$TMP/$(basename $SH)-read-ifs.txt
328 IFS=:
329 cat >$tmp <<'EOF'
330 \\a :b\: c:d\
331 e
332 EOF
333 read a b c d < $tmp
334 # Use printf because echo in dash/mksh interprets escapes, while it doesn't in
335 # bash.
336 printf "%s\n" "[$a|$b|$c|$d]"
337 ## stdout: [ \a |b: c|d e|]
338
339 #### Read with IFS=''
340 IFS=''
341 read x y <<EOF
342 a b c d
343 EOF
344 echo "[$x|$y]"
345 ## stdout: [ a b c d|]
346
347 #### Read should not respect C escapes.
348 # bash doesn't respect these, but other shells do. Gah! I think bash
349 # behavior makes more sense. It only escapes IFS.
350 echo '\a \b \c \d \e \f \g \h \x65 \145 \i' > $TMP/read-c.txt
351 read line < $TMP/read-c.txt
352 echo $line
353 ## stdout-json: "a b c d e f g h x65 145 i\n"
354 ## BUG ash stdout-json: "abcdefghx65 145 i\n"
355 ## BUG dash/zsh stdout-json: "\u0007 \u0008\n"
356 ## BUG mksh stdout-json: "\u0007 \u0008 d \u001b \u000c g h e 145 i\n"
357
358 #### Read builtin uses dynamic scope
359 f() {
360 read head << EOF
361 ref: refs/heads/dev/andy
362 EOF
363 }
364 f
365 echo $head
366 ## STDOUT:
367 ref: refs/heads/dev/andy
368 ## END
369
370 #### read -a reads into array
371
372 # read -a is used in bash-completion
373 # none of these shells implement it
374 case $SH in
375 *mksh|*dash|*zsh|*/ash)
376 exit 2;
377 ;;
378 esac
379
380 read -a myarray <<'EOF'
381 a b c\ d
382 EOF
383 argv.py "${myarray[@]}"
384
385 # arguments are ignored here
386 read -r -a array2 extra arguments <<'EOF'
387 a b c\ d
388 EOF
389 argv.py "${array2[@]}"
390 argv.py "${extra[@]}"
391 argv.py "${arguments[@]}"
392 ## status: 0
393 ## STDOUT:
394 ['a', 'b', 'c d']
395 ['a', 'b', 'c\\', 'd']
396 []
397 []
398 ## END
399 ## N-I dash/mksh/zsh/ash status: 2
400 ## N-I dash/mksh/zsh/ash stdout-json: ""
401
402 #### read -n with invalid arg
403 read -n not_a_number
404 echo status=$?
405 ## stdout: status=2
406 ## OK bash stdout: status=1
407 ## N-I zsh stdout-json: ""
408
409 #### read returns correct number of bytes without EOF
410 case $SH in
411 *bash|*osh) FLAG=n ;;
412 *mksh) FLAG=N ;;
413 *) exit ;; # other shells don't implement it, or hang
414 esac
415
416 i=0
417 while true; do
418 echo -n x
419
420 (( i++ ))
421
422 # TODO: Why does OSH hang without this test? Other shells are fine. I can't
423 # reproduce outside of sh_spec.py.
424 if test $i = 100; then
425 break
426 #true
427 fi
428 done | { read -$FLAG 3; echo $REPLY; }
429
430 ## status: 0
431 ## stdout: xxx
432 ## N-I dash/ash stdout-json: ""
433
434 # zsh appears to hang with -k
435 ## N-I zsh stdout-json: ""