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-json: "abcdef\n"
128 # N-I dash/ash stdout-json: "abcd\\u0065f\n"
129
130 ### echo -e with 8 digit unicode escape
131 flags='-e'
132 case $SH in */dash) flags='' ;; esac
133
134 echo $flags 'abcd\U00000065f'
135 # stdout-json: "abcdef\n"
136 # N-I dash/ash stdout-json: "abcd\\U00000065f\n"
137
138 ### \0377 is the highest octal byte
139 echo -en '\03777' | od -A n -t x1 | sed 's/ \+/ /g'
140 # stdout-json: " ff 37\n"
141 # N-I dash stdout-json: " 2d 65 6e 20 ff 37 0a\n"
142
143 ### \0400 is one more than the highest octal byte
144 # It is 256 % 256 which gets interpreted as a NUL byte.
145 echo -en '\04000' | od -A n -t x1 | sed 's/ \+/ /g'
146 # stdout-json: " 00 30\n"
147 # BUG ash stdout-json: " 20 30 30\n"
148 # N-I dash stdout-json: " 2d 65 6e 20\n"
149
150 ### \0777 is out of range
151 flags='-en'
152 case $SH in */dash) flags='-n' ;; esac
153
154 echo $flags '\0777' | od -A n -t x1 | sed 's/ \+/ /g'
155 # stdout-json: " ff\n"
156 # BUG mksh stdout-json: " c3 bf\n"
157 # BUG ash stdout-json: " 3f 37\n"
158
159 ### incomplete hex escape
160 echo -en 'abcd\x6' | od -A n -c | sed 's/ \+/ /g'
161 # stdout-json: " a b c d 006\n"
162 # N-I dash stdout-json: " - e n a b c d \\ x 6 \\n\n"
163
164 ### \x
165 # I consider mksh and zsh a bug because \x is not an escape
166 echo -e '\x' '\xg' | od -A n -c | sed 's/ \+/ /g'
167 # stdout-json: " \\ x \\ x g \\n\n"
168 # N-I dash stdout-json: " - e \\ x \\ x g \\n\n"
169 # BUG mksh/zsh stdout-json: " \\0 \\0 g \\n\n"
170
171 ### incomplete octal escape
172 flags='-en'
173 case $SH in */dash) flags='-n' ;; esac
174
175 echo $flags 'abcd\04' | od -A n -c | sed 's/ \+/ /g'
176 # stdout-json: " a b c d 004\n"
177
178 ### incomplete unicode escape
179 echo -en 'abcd\u006' | od -A n -c | sed 's/ \+/ /g'
180 # stdout-json: " a b c d 006\n"
181 # N-I dash stdout-json: " - e n a b c d \\ u 0 0 6 \\n\n"
182 # BUG ash stdout-json: " a b c d \\ u 0 0 6\n"
183
184 ### \u6
185 flags='-en'
186 case $SH in */dash) flags='-n' ;; esac
187
188 echo $flags '\u6' | od -A n -c | sed 's/ \+/ /g'
189 # stdout-json: " 006\n"
190 # N-I dash/ash stdout-json: " \\ u 6\n"
191
192 ### \0 \1 \8
193 # \0 is special, but \1 isn't in bash
194 # \1 is special in dash! geez
195 flags='-en'
196 case $SH in */dash) flags='-n' ;; esac
197
198 echo $flags '\0' '\1' '\8' | od -A n -c | sed 's/ \+/ /g'
199 # stdout-json: " \\0 \\ 1 \\ 8\n"
200 # BUG dash stdout-json: " 001 \\ 8\n"
201 # BUG ash stdout-json: " \\0 001 \\ 8\n"
202
203 ### Read builtin
204 # NOTE: there are TABS below
205 read x <<EOF
206 A B C D E
207 FG
208 EOF
209 echo "[$x]"
210 # stdout: [A B C D E]
211 # status: 0
212
213 ### Read from empty file
214 echo -n '' > $TMP/empty.txt
215 read x < $TMP/empty.txt
216 argv "status=$?" "$x"
217 # stdout: ['status=1', '']
218 # status: 0
219
220 ### Read builtin with no newline.
221 # This is odd because the variable is populated successfully. OSH/Oil might
222 # need a separate put reading feature that doesn't use IFS.
223 echo -n ZZZ | { read x; echo $?; echo $x; }
224 # stdout-json: "1\nZZZ\n"
225 # status: 0
226
227 ### Read builtin with multiple variables
228 # NOTE: there are TABS below
229 read x y z <<EOF
230 A B C D E
231 FG
232 EOF
233 echo "[$x/$y/$z]"
234 # stdout: [A/B/C D E]
235 # BUG dash stdout: [A/B/C D E ]
236 # status: 0
237
238 ### Read builtin with not enough variables
239 set -o errexit
240 set -o nounset # hm this doesn't change it
241 read x y z <<EOF
242 A B
243 EOF
244 echo /$x/$y/$z/
245 # stdout: /A/B//
246 # status: 0
247
248 ### Read -n (with $REPLY)
249 echo 12345 > $TMP/readn.txt
250 read -n 4 x < $TMP/readn.txt
251 read -n 2 < $TMP/readn.txt # Do it again with no variable
252 argv.py $x $REPLY
253 # stdout: ['1234', '12']
254 # N-I dash/zsh stdout: []
255
256 ### Read uses $REPLY (without -n)
257 echo 123 > $TMP/readreply.txt
258 read < $TMP/readreply.txt
259 echo $REPLY
260 # stdout: 123
261 # N-I dash stdout:
262
263 ### read -r ignores backslashes
264 echo 'one\ two' > $TMP/readr.txt
265 read escaped < $TMP/readr.txt
266 read -r raw < $TMP/readr.txt
267 argv "$escaped" "$raw"
268 # stdout: ['one two', 'one\\ two']
269
270 ### read -r with other backslash escapes
271 echo 'one\ two\x65three' > $TMP/readr.txt
272 read escaped < $TMP/readr.txt
273 read -r raw < $TMP/readr.txt
274 argv "$escaped" "$raw"
275 # mksh respects the hex escapes here, but other shells don't!
276 # stdout: ['one twox65three', 'one\\ two\\x65three']
277 # BUG mksh/zsh stdout: ['one twoethree', 'one\\ twoethree']
278
279 ### read with line continuation reads multiple physical lines
280 # NOTE: osh failing because of file descriptor issue. stdin has to be closed!
281 tmp=$TMP/$(basename $SH)-readr.txt
282 echo -e 'one\\\ntwo\n' > $tmp
283 read escaped < $tmp
284 read -r raw < $tmp
285 argv "$escaped" "$raw"
286 # stdout: ['onetwo', 'one\\']
287 # N-I dash stdout: ['-e onetwo', '-e one\\']
288
289 ### read multiple vars spanning many lines
290 read x y << 'EOF'
291 one-\
292 two three-\
293 four five-\
294 six
295 EOF
296 argv "$x" "$y" "$z"
297 # stdout: ['one-two', 'three-four five-six', '']
298
299 ### read -r with \n
300 echo '\nline' > $TMP/readr.txt
301 read escaped < $TMP/readr.txt
302 read -r raw < $TMP/readr.txt
303 argv "$escaped" "$raw"
304 # dash/mksh/zsh are bugs because at least the raw mode should let you read a
305 # literal \n.
306 # stdout: ['nline', '\\nline']
307 # BUG dash/mksh/zsh stdout: ['', '']
308
309 ### Read with IFS=$'\n'
310 # The leading spaces are stripped if they appear in IFS.
311 IFS=$(echo -e '\n')
312 read var <<EOF
313 a b c
314 d e f
315 EOF
316 echo "[$var]"
317 # stdout: [ a b c]
318 # N-I dash stdout: [a b c]
319
320 ### Read multiple lines with IFS=:
321 # The leading spaces are stripped if they appear in IFS.
322 # IFS chars are escaped with :.
323 tmp=$TMP/$(basename $SH)-read-ifs.txt
324 IFS=:
325 cat >$tmp <<'EOF'
326 \\a :b\: c:d\
327 e
328 EOF
329 read a b c d < $tmp
330 # Use printf because echo in dash/mksh interprets escapes, while it doesn't in
331 # bash.
332 printf "%s\n" "[$a|$b|$c|$d]"
333 # stdout: [ \a |b: c|d e|]
334
335 ### Read with IFS=''
336 IFS=''
337 read x y <<EOF
338 a b c d
339 EOF
340 echo "[$x|$y]"
341 # stdout: [ a b c d|]
342
343 ### Read should not respect C escapes.
344 # bash doesn't respect these, but other shells do. Gah! I think bash
345 # behavior makes more sense. It only escapes IFS.
346 echo '\a \b \c \d \e \f \g \h \x65 \145 \i' > $TMP/read-c.txt
347 read line < $TMP/read-c.txt
348 echo $line
349 # stdout-json: "a b c d e f g h x65 145 i\n"
350 # BUG ash stdout-json: "abcdefghx65 145 i\n"
351 # BUG dash/zsh stdout-json: "\u0007 \u0008\n"
352 # BUG mksh stdout-json: "\u0007 \u0008 d \u001b \u000c g h e 145 i\n"
353