Skip to content

Commit 6431d76

Browse files
author
Andy C
committed
[builtin] Fix bugs in mapfile -t and read --raw-line
Caused by a broken underlying ReadLineSlowly() This is issue #2287, reported originally with ble.sh in #1069.
1 parent 96b271d commit 6431d76

File tree

4 files changed

+65
-25
lines changed

4 files changed

+65
-25
lines changed

builtin/io_osh.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,13 @@ def Run(self, cmd_val):
131131
while True:
132132
# bash uses this slow algorithm; YSH could provide read --all-lines
133133
try:
134-
line = read_osh.ReadLineSlowly(self.cmd_ev, with_eol=not arg.t)
134+
line, eof = read_osh.ReadLineSlowly(self.cmd_ev,
135+
with_eol=not arg.t)
135136
except pyos.ReadError as e:
136137
self.errfmt.PrintMessage("mapfile: read() error: %s" %
137138
posix.strerror(e.err_num))
138139
return 1
139-
if len(line) == 0:
140+
if eof:
140141
break
141142
lines.append(line)
142143

@@ -152,7 +153,6 @@ class Cat(vm._Builtin):
152153

153154
def __init__(self):
154155
# type: () -> None
155-
"""Empty constructor for mycpp."""
156156
vm._Builtin.__init__(self)
157157

158158
def Run(self, cmd_val):

builtin/read_osh.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,9 @@ def _ReadPortion(delim_byte, max_chars, cmd_ev):
155155
156156
The delimiter is not included in the result.
157157
"""
158-
eof = False
159158
ch_array = [] # type: List[int]
159+
eof = False
160+
160161
bytes_read = 0
161162
while True:
162163
if max_chars >= 0 and bytes_read >= max_chars:
@@ -186,7 +187,7 @@ def _ReadPortion(delim_byte, max_chars, cmd_ev):
186187

187188

188189
def ReadLineSlowly(cmd_ev, with_eol=True):
189-
# type: (CommandEvaluator, bool) -> str
190+
# type: (CommandEvaluator, bool) -> Tuple[str, bool]
190191
"""Read a line from stdin, unbuffered
191192
192193
Used by mapfile and read --raw-line.
@@ -196,8 +197,11 @@ def ReadLineSlowly(cmd_ev, with_eol=True):
196197
with read(0, 1).
197198
"""
198199
ch_array = [] # type: List[int]
200+
eof = False
201+
is_first_byte = True
199202
while True:
200203
ch, err_num = pyos.ReadByte(0)
204+
#log(' ch %d', ch)
201205

202206
if ch < 0:
203207
if err_num == EINTR:
@@ -207,17 +211,21 @@ def ReadLineSlowly(cmd_ev, with_eol=True):
207211
raise pyos.ReadError(err_num)
208212

209213
elif ch == pyos.EOF_SENTINEL:
214+
if is_first_byte:
215+
eof = True
216+
break
217+
218+
elif ch == pyos.NEWLINE_CH:
219+
if with_eol:
220+
ch_array.append(ch)
210221
break
211222

212223
else:
213224
ch_array.append(ch)
214225

215-
if ch == pyos.NEWLINE_CH:
216-
if not with_eol:
217-
ch_array.pop()
218-
break
226+
is_first_byte = False
219227

220-
return pyutil.ChArrayToString(ch_array)
228+
return pyutil.ChArrayToString(ch_array), eof
221229

222230

223231
def ReadAll():
@@ -371,10 +379,10 @@ def _ReadYsh(self, arg, arg_r, cmd_val):
371379
status = 0
372380

373381
elif arg.raw_line: # read --raw-line is unbuffered
374-
contents = ReadLineSlowly(self.cmd_ev, with_eol=arg.with_eol)
382+
contents, eof = ReadLineSlowly(self.cmd_ev, with_eol=arg.with_eol)
375383
#log('EOF %s', eof)
376384
#status = 1 if eof else 0
377-
status = 0 if len(contents) else 1
385+
status = 1 if eof else 0
378386

379387
elif arg.all: # read --all
380388
contents = ReadAll()

spec/builtin-bash.test.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,29 @@ printf '%s\r\n' {1..5..2} | {
144144
## N-I dash/mksh/zsh/ash STDOUT:
145145
## END
146146

147+
#### mapfile -t bugs (ble.sh)
148+
149+
# empty line
150+
mapfile -t lines <<< $'hello\n\nworld'
151+
echo len=${#lines[@]}
152+
#declare -p lines
153+
154+
# initial newline
155+
mapfile -t lines <<< $'\nhello'
156+
echo len=${#lines[@]}
157+
#declare -p lines
158+
159+
# trailing newline
160+
mapfile -t lines <<< $'hello\n'
161+
echo len=${#lines[@]}
162+
#declare -p lines
163+
164+
## STDOUT:
165+
len=3
166+
len=2
167+
len=2
168+
## END
169+
147170
#### mapfile (store position): -O start
148171
type mapfile >/dev/null 2>&1 || exit 0
149172
printf '%s\n' a{0..2} | {

spec/ysh-builtins.test.sh

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -172,42 +172,51 @@ len=2
172172
pass
173173
## END
174174

175-
#### read --raw-line handles line without end, --with-eol
175+
#### read --raw-line in a loop
176176

177-
write --end '' $'a\nb\n' | while read --raw-line; do
177+
write --end '' $'\na\nb\n\n' | while read --raw-line; do
178178
pp test_ (_reply)
179179
done
180180

181181
echo
182182

183-
write --end '' $'a\nb' | while read --raw-line; do
183+
write --end '' $'a\n\nb' | while read --raw-line; do
184184
pp test_ (_reply)
185185
done
186186

187-
echo
188187

189-
write --end '' $'a\nb\n' | while read --raw-line --with-eol; do
188+
## STDOUT:
189+
(Str) ""
190+
(Str) "a"
191+
(Str) "b"
192+
(Str) ""
193+
194+
(Str) "a"
195+
(Str) ""
196+
(Str) "b"
197+
## END
198+
199+
200+
#### read --raw-line --with-eol in a loop
201+
202+
write --end '' $'\na\nb\n\n' | while read --raw-line --with-eol; do
190203
pp test_ (_reply)
191204
done
192205

193206
echo
194207

195-
write --end '' $'a\nb' | while read --raw-line --with-eol; do
208+
write --end '' $'a\n\nb' | while read --raw-line --with-eol; do
196209
pp test_ (_reply)
197210
done
198211

199-
200212
## STDOUT:
201-
(Str) "a"
202-
(Str) "b"
203-
204-
(Str) "a"
205-
(Str) "b"
206-
213+
(Str) "\n"
207214
(Str) "a\n"
208215
(Str) "b\n"
216+
(Str) "\n"
209217

210218
(Str) "a\n"
219+
(Str) "\n"
211220
(Str) "b"
212221
## END
213222

0 commit comments

Comments
 (0)