1 #
2 # echo, read, mapfile
3 # TODO mapfile options: -c, -C, -u, etc.
4
5 #### echo dashes
6 echo -
7 echo --
8 echo ---
9 ## stdout-json: "-\n--\n---\n"
10 ## BUG zsh stdout-json: "\n--\n---\n"
11
12 #### echo backslashes
13 echo \\
14 echo '\'
15 echo '\\'
16 echo "\\"
17 ## STDOUT:
18 \
19 \
20 \\
21 \
22 ## BUG dash/mksh/zsh STDOUT:
23 \
24 \
25 \
26 \
27 ## END
28
29 #### echo -e backslashes
30 echo -e \\
31 echo -e '\'
32 echo -e '\\'
33 echo -e "\\"
34 ## STDOUT:
35 \
36 \
37 \
38 \
39 ## N-I dash STDOUT:
40 -e \
41 -e \
42 -e \
43 -e \
44 ## END
45
46 #### echo -en
47 echo -en 'abc\ndef\n'
48 ## stdout-json: "abc\ndef\n"
49 ## N-I dash stdout-json: "-en abc\ndef\n\n"
50
51 #### echo -ez (invalid flag)
52 # bash differs from the other three shells, but its behavior is possibly more
53 # sensible, if you're going to ignore the error. It doesn't make sense for
54 # the 'e' to mean 2 different things simultaneously: flag and literal to be
55 # printed.
56 echo -ez 'abc\n'
57 ## stdout-json: "-ez abc\\n\n"
58 ## OK dash/mksh/zsh stdout-json: "-ez abc\n\n"
59
60 #### echo -e with embedded newline
61 flags='-e'
62 case $SH in dash) flags='' ;; esac
63
64 echo $flags 'foo
65 bar'
66 ## STDOUT:
67 foo
68 bar
69 ## END
70
71 #### echo -e line continuation
72 flags='-e'
73 case $SH in dash) flags='' ;; esac
74
75 echo $flags 'foo\
76 bar'
77 ## STDOUT:
78 foo\
79 bar
80 ## END
81
82 #### echo -e with C escapes
83 # https://www.gnu.org/software/bash/manual/bashref.html#Bourne-Shell-Builtins
84 # not sure why \c is like NUL?
85 # zsh doesn't allow \E for some reason.
86 echo -e '\a\b\d\e\f'
87 ## stdout-json: "\u0007\u0008\\d\u001b\u000c\n"
88 ## N-I dash stdout-json: "-e \u0007\u0008\\d\\e\u000c\n"
89
90 #### echo -e with whitespace C escapes
91 echo -e '\n\r\t\v'
92 ## stdout-json: "\n\r\t\u000b\n"
93 ## N-I dash stdout-json: "-e \n\r\t\u000b\n"
94
95 #### \0
96 echo -e 'ab\0cd'
97 ## stdout-json: "ab\u0000cd\n"
98 ## N-I dash stdout-json: "-e ab\u0000cd\n"
99
100 #### \c stops processing input
101 flags='-e'
102 case $SH in dash) flags='' ;; esac
103
104 echo $flags xy 'ab\cde' 'zzz'
105 ## stdout-json: "xy ab"
106 ## N-I mksh stdout-json: "xy abde zzz"
107
108 #### echo -e with hex escape
109 echo -e 'abcd\x65f'
110 ## stdout-json: "abcdef\n"
111 ## N-I dash stdout-json: "-e abcd\\x65f\n"
112
113 #### echo -e with octal escape
114 flags='-e'
115 case $SH in dash) flags='' ;; esac
116
117 echo $flags 'abcd\044e'
118 ## stdout-json: "abcd$e\n"
119
120 #### echo -e with 4 digit unicode escape
121 flags='-e'
122 case $SH in dash) flags='' ;; esac
123
124 echo $flags 'abcd\u0065f'
125 ## STDOUT:
126 abcdef
127 ## END
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:
136 abcdef
137 ## END
138 ## N-I dash/ash stdout-json: "abcd\\U00000065f\n"
139
140 #### \0377 is the highest octal byte
141 echo -en '\03777' | od -A n -t x1 | sed 's/ \+/ /g'
142 ## stdout-json: " ff 37\n"
143 ## N-I dash stdout-json: " 2d 65 6e 20 ff 37 0a\n"
144
145 #### \0400 is one more than the highest octal byte
146 # It is 256 % 256 which gets interpreted as a NUL byte.
147 echo -en '\04000' | od -A n -t x1 | sed 's/ \+/ /g'
148 ## stdout-json: " 00 30\n"
149 ## BUG ash stdout-json: " 20 30 30\n"
150 ## N-I dash stdout-json: " 2d 65 6e 20 00 30 0a\n"
151
152 #### \0777 is out of range
153 flags='-en'
154 case $SH in dash) flags='-n' ;; esac
155
156 echo $flags '\0777' | od -A n -t x1 | sed 's/ \+/ /g'
157 ## stdout-json: " ff\n"
158 ## BUG mksh stdout-json: " c3 bf\n"
159 ## BUG ash stdout-json: " 3f 37\n"
160
161 #### incomplete hex escape
162 echo -en 'abcd\x6' | od -A n -c | sed 's/ \+/ /g'
163 ## stdout-json: " a b c d 006\n"
164 ## N-I dash stdout-json: " - e n a b c d \\ x 6 \\n\n"
165
166 #### \x
167 # I consider mksh and zsh a bug because \x is not an escape
168 echo -e '\x' '\xg' | od -A n -c | sed 's/ \+/ /g'
169 ## stdout-json: " \\ x \\ x g \\n\n"
170 ## N-I dash stdout-json: " - e \\ x \\ x g \\n\n"
171 ## BUG mksh/zsh stdout-json: " \\0 \\0 g \\n\n"
172
173 #### incomplete octal escape
174 flags='-en'
175 case $SH in dash) flags='-n' ;; esac
176
177 echo $flags 'abcd\04' | od -A n -c | sed 's/ \+/ /g'
178 ## stdout-json: " a b c d 004\n"
179
180 #### incomplete unicode escape
181 echo -en 'abcd\u006' | od -A n -c | sed 's/ \+/ /g'
182 ## stdout-json: " a b c d 006\n"
183 ## N-I dash stdout-json: " - e n a b c d \\ u 0 0 6 \\n\n"
184 ## BUG ash stdout-json: " a b c d \\ u 0 0 6\n"
185
186 #### \u6
187 flags='-en'
188 case $SH in dash) flags='-n' ;; esac
189
190 echo $flags '\u6' | od -A n -c | sed 's/ \+/ /g'
191 ## stdout-json: " 006\n"
192 ## N-I dash/ash stdout-json: " \\ u 6\n"
193
194 #### \0 \1 \8
195 # \0 is special, but \1 isn't in bash
196 # \1 is special in dash! geez
197 flags='-en'
198 case $SH in dash) flags='-n' ;; esac
199
200 echo $flags '\0' '\1' '\8' | od -A n -c | sed 's/ \+/ /g'
201 ## stdout-json: " \\0 \\ 1 \\ 8\n"
202 ## BUG dash/ash stdout-json: " \\0 001 \\ 8\n"
203
204 #### Read builtin
205 # NOTE: there are TABS below
206 read x <<EOF
207 A B C D E
208 FG
209 EOF
210 echo "[$x]"
211 ## stdout: [A B C D E]
212 ## status: 0
213
214 #### Read from empty file
215 echo -n '' > $TMP/empty.txt
216 read x < $TMP/empty.txt
217 argv.py "status=$?" "$x"
218
219 # No variable name, behaves the same
220 read < $TMP/empty.txt
221 argv.py "status=$?" "$REPLY"
222
223 ## STDOUT:
224 ['status=1', '']
225 ['status=1', '']
226 ## END
227 ## OK dash STDOUT:
228 ['status=1', '']
229 ['status=2', '']
230 ## END
231 ## status: 0
232
233 #### read /dev/null
234 read -n 1 </dev/null
235 echo $?
236 ## STDOUT:
237 1
238 ## END
239 ## OK dash stdout: 2
240
241 #### read with zero args
242 echo | read
243 echo status=$?
244 ## STDOUT:
245 status=0
246 ## END
247 ## BUG dash STDOUT:
248 status=2
249 ## END
250
251 #### Read builtin with no newline.
252 # This is odd because the variable is populated successfully. OSH/Oil might
253 # need a separate put reading feature that doesn't use IFS.
254 echo -n ZZZ | { read x; echo $?; echo $x; }
255 ## stdout-json: "1\nZZZ\n"
256 ## status: 0
257
258 #### Read builtin with multiple variables
259 # NOTE: there are TABS below
260 read x y z <<EOF
261 A B C D E
262 FG
263 EOF
264 echo "[$x/$y/$z]"
265 ## stdout: [A/B/C D E]
266 ## status: 0
267
268 #### Read builtin with not enough variables
269 set -o errexit
270 set -o nounset # hm this doesn't change it
271 read x y z <<EOF
272 A B
273 EOF
274 echo /$x/$y/$z/
275 ## stdout: /A/B//
276 ## status: 0
277
278 #### Read -n (with $REPLY)
279 echo 12345 > $TMP/readn.txt
280 read -n 4 x < $TMP/readn.txt
281 read -n 2 < $TMP/readn.txt # Do it again with no variable
282 argv.py $x $REPLY
283 ## stdout: ['1234', '12']
284 ## N-I dash/zsh stdout: []
285
286 #### IFS= read -n (OSH regression: value saved in tempenv)
287 echo XYZ > "$TMP/readn.txt"
288 IFS= TMOUT= read -n 1 char < "$TMP/readn.txt"
289 argv.py "$char"
290 ## stdout: ['X']
291 ## N-I dash/zsh stdout: ['']
292
293 #### read -n with invalid arg
294 read -n not_a_number
295 echo status=$?
296 ## stdout: status=2
297 ## OK bash stdout: status=1
298 ## N-I zsh stdout-json: ""
299
300 #### read -n from pipe
301 case $SH in (dash|ash|zsh) exit ;; esac
302
303 echo abcxyz | { read -n 3; echo reply=$REPLY; }
304 ## status: 0
305 ## stdout: reply=abc
306 ## N-I dash/ash/zsh stdout-json: ""
307
308 # zsh appears to hang with -k
309 ## N-I zsh stdout-json: ""
310
311 #### Read uses $REPLY (without -n)
312 echo 123 > $TMP/readreply.txt
313 read < $TMP/readreply.txt
314 echo $REPLY
315 ## stdout: 123
316 ## N-I dash stdout:
317
318 #### read -r ignores backslashes
319 echo 'one\ two' > $TMP/readr.txt
320 read escaped < $TMP/readr.txt
321 read -r raw < $TMP/readr.txt
322 argv.py "$escaped" "$raw"
323 ## stdout: ['one two', 'one\\ two']
324
325 #### read -r with other backslash escapes
326 echo 'one\ two\x65three' > $TMP/readr.txt
327 read escaped < $TMP/readr.txt
328 read -r raw < $TMP/readr.txt
329 argv.py "$escaped" "$raw"
330 # mksh respects the hex escapes here, but other shells don't!
331 ## stdout: ['one twox65three', 'one\\ two\\x65three']
332 ## BUG mksh/zsh stdout: ['one twoethree', 'one\\ twoethree']
333
334 #### read with line continuation reads multiple physical lines
335 # NOTE: osh failing because of file descriptor issue. stdin has to be closed!
336 tmp=$TMP/$(basename $SH)-readr.txt
337 echo -e 'one\\\ntwo\n' > $tmp
338 read escaped < $tmp
339 read -r raw < $tmp
340 argv.py "$escaped" "$raw"
341 ## stdout: ['onetwo', 'one\\']
342 ## N-I dash stdout: ['-e onetwo', '-e one\\']
343
344 #### read multiple vars spanning many lines
345 read x y << 'EOF'
346 one-\
347 two three-\
348 four five-\
349 six
350 EOF
351 argv.py "$x" "$y" "$z"
352 ## stdout: ['one-two', 'three-four five-six', '']
353
354 #### read -r with \n
355 echo '\nline' > $TMP/readr.txt
356 read escaped < $TMP/readr.txt
357 read -r raw < $TMP/readr.txt
358 argv.py "$escaped" "$raw"
359 # dash/mksh/zsh are bugs because at least the raw mode should let you read a
360 # literal \n.
361 ## stdout: ['nline', '\\nline']
362 ## BUG dash/mksh/zsh stdout: ['', '']
363
364 #### read -s from pipe, not a terminal
365 case $SH in (dash|zsh) exit ;; esac
366
367 # It's hard to really test this because it requires a terminal. We hit a
368 # different code path when reading through a pipe. There can be bugs there
369 # too!
370
371 echo foo | { read -s; echo $REPLY; }
372 echo bar | { read -n 2 -s; echo $REPLY; }
373
374 # Hm no exit 1 here? Weird
375 echo b | { read -n 2 -s; echo $?; echo $REPLY; }
376 ## STDOUT:
377 foo
378 ba
379 0
380 b
381 ## END
382 ## N-I dash/zsh stdout-json: ""
383
384 #### Read with IFS=$'\n'
385 # The leading spaces are stripped if they appear in IFS.
386 IFS=$(echo -e '\n')
387 read var <<EOF
388 a b c
389 d e f
390 EOF
391 echo "[$var]"
392 ## stdout: [ a b c]
393 ## N-I dash stdout: [a b c]
394
395 #### Read multiple lines with IFS=:
396 # The leading spaces are stripped if they appear in IFS.
397 # IFS chars are escaped with :.
398 tmp=$TMP/$(basename $SH)-read-ifs.txt
399 IFS=:
400 cat >$tmp <<'EOF'
401 \\a :b\: c:d\
402 e
403 EOF
404 read a b c d < $tmp
405 # Use printf because echo in dash/mksh interprets escapes, while it doesn't in
406 # bash.
407 printf "%s\n" "[$a|$b|$c|$d]"
408 ## stdout: [ \a |b: c|d e|]
409
410 #### Read with IFS=''
411 IFS=''
412 read x y <<EOF
413 a b c d
414 EOF
415 echo "[$x|$y]"
416 ## stdout: [ a b c d|]
417
418 #### Read should not respect C escapes.
419 # bash doesn't respect these, but other shells do. Gah! I think bash
420 # behavior makes more sense. It only escapes IFS.
421 echo '\a \b \c \d \e \f \g \h \x65 \145 \i' > $TMP/read-c.txt
422 read line < $TMP/read-c.txt
423 echo $line
424 ## stdout-json: "a b c d e f g h x65 145 i\n"
425 ## BUG ash stdout-json: "abcdefghx65 145 i\n"
426 ## BUG dash/zsh stdout-json: "\u0007 \u0008\n"
427 ## BUG mksh stdout-json: "\u0007 \u0008 d \u001b \u000c g h e 145 i\n"
428
429 #### Read builtin uses dynamic scope
430 f() {
431 read head << EOF
432 ref: refs/heads/dev/andy
433 EOF
434 }
435 f
436 echo $head
437 ## STDOUT:
438 ref: refs/heads/dev/andy
439 ## END
440
441 #### read -a reads into array
442
443 # read -a is used in bash-completion
444 # none of these shells implement it
445 case $SH in
446 *mksh|*dash|*zsh|*/ash)
447 exit 2;
448 ;;
449 esac
450
451 read -a myarray <<'EOF'
452 a b c\ d
453 EOF
454 argv.py "${myarray[@]}"
455
456 # arguments are ignored here
457 read -r -a array2 extra arguments <<'EOF'
458 a b c\ d
459 EOF
460 argv.py "${array2[@]}"
461 argv.py "${extra[@]}"
462 argv.py "${arguments[@]}"
463 ## status: 0
464 ## STDOUT:
465 ['a', 'b', 'c d']
466 ['a', 'b', 'c\\', 'd']
467 []
468 []
469 ## END
470 ## N-I dash/mksh/zsh/ash status: 2
471 ## N-I dash/mksh/zsh/ash stdout-json: ""
472
473 #### read -d : (colon-separated records)
474 printf a,b,c:d,e,f:g,h,i | {
475 IFS=,
476 read -d : v1
477 echo "v1=$v1"
478 read -d : v1 v2
479 echo "v1=$v1 v2=$v2"
480 read -d : v1 v2 v3
481 echo "v1=$v1 v2=$v2 v3=$v3"
482 }
483 ## STDOUT:
484 v1=a,b,c
485 v1=d v2=e,f
486 v1=g v2=h v3=i
487 ## END
488 ## N-I dash STDOUT:
489 v1=
490 v1= v2=
491 v1= v2= v3=
492 ## END
493 ## BUG ash STDOUT:
494 v1=a,b,c
495 v1=d,e,f v2=
496 v1=g,h,i v2= v3=
497 ## END
498
499 #### read -d '' (null-separated records)
500 printf 'a,b,c\0d,e,f\0g,h,i' | {
501 IFS=,
502 read -d '' v1
503 echo "v1=$v1"
504 read -d '' v1 v2
505 echo "v1=$v1 v2=$v2"
506 read -d '' v1 v2 v3
507 echo "v1=$v1 v2=$v2 v3=$v3"
508 }
509 ## STDOUT:
510 v1=a,b,c
511 v1=d v2=e,f
512 v1=g v2=h v3=i
513 ## END
514 ## N-I dash STDOUT:
515 v1=
516 v1= v2=
517 v1= v2= v3=
518 ## END
519 ## BUG ash STDOUT:
520 v1=a,b,cd,e,fg,h,i
521 v1= v2=
522 v1= v2= v3=
523 ## END
524
525 #### read -rd
526 read -rd '' var <<EOF
527 foo
528 bar
529 EOF
530 echo "$var"
531 ## STDOUT:
532 foo
533 bar
534 ## END
535 ## N-I dash stdout-json: "\n"
536
537 #### read -d when there's no delimiter
538 { read -d : part
539 echo $part $?
540 read -d : part
541 echo $part $?
542 } <<EOF
543 foo:bar
544 EOF
545 ## STDOUT:
546 foo 0
547 bar 1
548 ## END
549 ## N-I dash STDOUT:
550 2
551 2
552 ## END
553
554 #### read -t 0 tests if input is available
555 case $SH in (dash|zsh|mksh) exit ;; esac
556
557 # is there input available?
558 read -t 0 < /dev/null
559 echo $?
560
561 # floating point
562 read -t 0.0 < /dev/null
563 echo $?
564
565 # floating point
566 echo foo | { read -t 0; echo reply=$REPLY; }
567 echo $?
568
569 ## STDOUT:
570 0
571 0
572 reply=
573 0
574 ## END
575 ## N-I dash/zsh/mksh stdout-json: ""
576
577 #### read -t 0.5
578 case $SH in (dash) exit ;; esac
579
580 read -t 0.5 < /dev/null
581 echo $?
582
583 ## STDOUT:
584 1
585 ## END
586 ## BUG zsh/mksh STDOUT:
587 1
588 ## END
589 ## N-I dash stdout-json: ""
590
591 #### read -t -0.5 is invalid
592 # bash appears to just take the absolute value?
593
594 read -t -0.5 < /dev/null
595 echo $?
596
597 ## STDOUT:
598 2
599 ## END
600 ## BUG bash STDOUT:
601 1
602 ## END
603 ## BUG zsh stdout-json: ""
604 ## BUG zsh status: 1
605
606 #### read -u
607 case $SH in (dash|mksh) exit ;; esac
608
609 # file descriptor
610 read -u 3 3<<EOF
611 hi
612 EOF
613 echo reply=$REPLY
614 ## STDOUT:
615 reply=hi
616 ## END
617 ## N-I dash/mksh stdout-json: ""
618
619 #### read -u syntax error
620 read -u -3
621 echo status=$?
622 ## STDOUT:
623 status=2
624 ## END
625 ## OK bash/zsh STDOUT:
626 status=1
627 ## END
628
629 #### read -N doesn't respect delimiter, while read -n does
630 case $SH in (dash|zsh|ash) exit ;; esac
631
632 echo foobar | { read -n 5 -d b; echo $REPLY; }
633 echo foobar | { read -N 5 -d b; echo $REPLY; }
634 ## STDOUT:
635 foo
636 fooba
637 ## END
638 ## OK mksh STDOUT:
639 fooba
640 fooba
641 ## END
642 ## N-I dash/zsh/ash stdout-json: ""
643
644 #### read -p (not fully tested)
645
646 # hm DISABLED if we're not going to the terminal
647 # so we're only testing that it accepts the flag here
648
649 case $SH in (dash|mksh|zsh) exit ;; esac
650
651 echo hi | { read -p 'P'; echo $REPLY; }
652 echo hi | { read -p 'P' -n 1; echo $REPLY; }
653 ## STDOUT:
654 hi
655 h
656 ## END
657 ## stderr-json: ""
658 ## N-I dash/mksh/zsh stdout-json: ""
659
660 #### read usage
661 read -n -1
662 echo status=$?
663 ## STDOUT:
664 status=2
665 ## END
666 ## OK bash stdout: status=1
667 ## BUG mksh stdout-json: ""
668 # zsh gives a fatal error? seems inconsistent
669 ## BUG zsh stdout-json: ""
670 ## BUG zsh status: 1
671
672 #### read with smooshed args
673 echo hi | { read -rn1 var; echo var=$var; }
674 ## STDOUT:
675 var=h
676 ## END
677 ## N-I dash/zsh STDOUT:
678 var=
679 ## END
680
681 #### read -r -d '' for NUL strings, e.g. find -print0
682
683
684 case $SH in (dash|zsh|mksh) exit ;; esac # NOT IMPLEMENTED
685
686 mkdir -p read0
687 cd read0
688 rm -f *
689
690 touch a\\b\\c\\d # -r is necessary!
691
692 find . -type f -a -print0 | { read -r -d ''; echo "[$REPLY]"; }
693
694 ## STDOUT:
695 [./a\b\c\d]
696 ## END
697 ## N-I dash/zsh/mksh STDOUT:
698 ## END
699