OILS / spec / builtin-io.test.sh View on Github | oilshell.org

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