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
494 #### read -d '' (null-separated records)
495 printf 'a,b,c\0d,e,f\0g,h,i' | {
496 IFS=,
497 read -d '' v1
498 echo "v1=$v1"
499 read -d '' v1 v2
500 echo "v1=$v1 v2=$v2"
501 read -d '' v1 v2 v3
502 echo "v1=$v1 v2=$v2 v3=$v3"
503 }
504 ## STDOUT:
505 v1=a,b,c
506 v1=d v2=e,f
507 v1=g v2=h v3=i
508 ## END
509 ## N-I dash STDOUT:
510 v1=
511 v1= v2=
512 v1= v2= v3=
513 ## END
514
515 #### read -rd
516 read -rd '' var <<EOF
517 foo
518 bar
519 EOF
520 echo "$var"
521 ## STDOUT:
522 foo
523 bar
524 ## END
525 ## N-I dash stdout-json: "\n"
526
527 #### read -d when there's no delimiter
528 { read -d : part
529 echo $part $?
530 read -d : part
531 echo $part $?
532 } <<EOF
533 foo:bar
534 EOF
535 ## STDOUT:
536 foo 0
537 bar 1
538 ## END
539 ## N-I dash STDOUT:
540 2
541 2
542 ## END
543
544 #### read -t 0 tests if input is available
545 case $SH in (dash|zsh|mksh) exit ;; esac
546
547 # is there input available?
548 read -t 0 < /dev/null
549 echo $?
550
551 # floating point
552 read -t 0.0 < /dev/null
553 echo $?
554
555 # floating point
556 echo foo | { read -t 0; echo reply=$REPLY; }
557 echo $?
558
559 ## STDOUT:
560 0
561 0
562 reply=
563 0
564 ## END
565 ## N-I dash/zsh/mksh stdout-json: ""
566
567 #### read -t 0.5
568 case $SH in (dash) exit ;; esac
569
570 read -t 0.5 < /dev/null
571 echo $?
572
573 ## STDOUT:
574 1
575 ## END
576 ## BUG zsh/mksh STDOUT:
577 1
578 ## END
579 ## N-I dash stdout-json: ""
580
581 #### read -t -0.5 is invalid
582 # bash appears to just take the absolute value?
583
584 read -t -0.5 < /dev/null
585 echo $?
586
587 ## STDOUT:
588 2
589 ## END
590 ## BUG bash STDOUT:
591 1
592 ## END
593 ## BUG zsh stdout-json: ""
594 ## BUG zsh status: 1
595
596 #### read -u
597 case $SH in (dash|mksh) exit ;; esac
598
599 # file descriptor
600 read -u 3 3<<EOF
601 hi
602 EOF
603 echo reply=$REPLY
604 ## STDOUT:
605 reply=hi
606 ## END
607 ## N-I dash/mksh stdout-json: ""
608
609 #### read -u syntax error
610 read -u -3
611 echo status=$?
612 ## STDOUT:
613 status=2
614 ## END
615 ## OK bash/zsh STDOUT:
616 status=1
617 ## END
618
619 #### read -N doesn't respect delimiter, while read -n does
620 case $SH in (dash|zsh|ash) exit ;; esac
621
622 echo foobar | { read -n 5 -d b; echo $REPLY; }
623 echo foobar | { read -N 5 -d b; echo $REPLY; }
624 ## STDOUT:
625 foo
626 fooba
627 ## END
628 ## OK mksh STDOUT:
629 fooba
630 fooba
631 ## END
632 ## N-I dash/zsh/ash stdout-json: ""
633
634 #### read -p (not fully tested)
635
636 # hm DISABLED if we're not going to the terminal
637 # so we're only testing that it accepts the flag here
638
639 case $SH in (dash|mksh|zsh) exit ;; esac
640
641 echo hi | { read -p 'P'; echo $REPLY; }
642 echo hi | { read -p 'P' -n 1; echo $REPLY; }
643 ## STDOUT:
644 hi
645 h
646 ## END
647 ## stderr-json: ""
648 ## N-I dash/mksh/zsh stdout-json: ""
649
650 #### read usage
651 read -n -1
652 echo status=$?
653 ## STDOUT:
654 status=2
655 ## END
656 ## OK bash stdout: status=1
657 ## BUG mksh stdout-json: ""
658 # zsh gives a fatal error? seems inconsistent
659 ## BUG zsh stdout-json: ""
660 ## BUG zsh status: 1
661
662 #### read with smooshed args
663 echo hi | { read -rn1 var; echo var=$var; }
664 ## STDOUT:
665 var=h
666 ## END
667 ## N-I dash/zsh STDOUT:
668 var=
669 ## END
670
671 #### read -r -d '' for NUL strings, e.g. find -print0
672
673
674 case $SH in (dash|zsh|mksh) exit ;; esac # NOT IMPLEMENTED
675
676 mkdir -p read0
677 cd read0
678 rm -f *
679
680 touch a\\b\\c\\d # -r is necessary!
681
682 find . -type f -a -print0 | { read -r -d ''; echo "[$REPLY]"; }
683
684 ## STDOUT:
685 [./a\b\c\d]
686 ## END
687 ## N-I dash/zsh/mksh STDOUT:
688 ## END
689
690
691 #### redirection from directory is non-fatal error)
692
693 # This tickles an infinite loop bug in our version of mksh! TODO: upgrade the
694 # version and enable this
695 case $SH in (mksh) return ;; esac
696
697 cd $TMP
698 mkdir -p dir
699 read x < ./dir
700 echo status=$?
701
702 ## STDOUT:
703 status=1
704 ## END
705 # OK mksh stdout: status=2
706 ## OK mksh stdout-json: ""
707
708 #### read -n from directory
709
710 case $SH in (dash|ash) return ;; esac # not implemented
711
712 # same hanging bug
713 case $SH in (mksh) return ;; esac
714
715 mkdir -p dir
716 read -n 3 x < ./dir
717 echo status=$?
718 ## STDOUT:
719 status=1
720 ## END
721 ## OK mksh stdout-json: ""
722 ## N-I dash/ash stdout-json: ""
723
724 #### mapfile from directory (bash doesn't handle errors)
725 case $SH in (dash|ash|mksh|zsh) return ;; esac # not implemented
726
727 mkdir -p dir
728 mapfile $x < ./dir
729 echo status=$?
730
731 ## STDOUT:
732 status=1
733 ## END
734 ## BUG bash STDOUT:
735 status=0
736 ## END
737 ## N-I dash/ash/mksh/zsh stdout-json: ""
738
739 #### Redirect to directory
740 mkdir -p dir
741
742 echo foo > ./dir
743 echo status=$?
744 printf foo > ./dir
745 echo status=$?
746
747 ## STDOUT:
748 status=1
749 status=1
750 ## END
751 ## OK dash STDOUT:
752 status=2
753 status=2
754 ## END
755