Re: sh read on files missing the final newline

Robert Elz <[email protected]> Tue, 07 Jul 2026 07:30:24 +0700
Newsgroups gmane.os.netbsd.devel.userlevel
Message-ID <[email protected]>
    Date:        Mon, 6 Jul 2026 18:27:01 +0200
    From:        Edgar Fu=DF <ef=40math.uni-bonn.de>
    Message-ID:  <akvXVSX/fA6CsV4J=40trav.math.uni-bonn.de>

  =7C I stumbled over the fact that sh's read returned 1 on the final par=
t=20
  =7C (call it a line or not) of a file when it didn't end in a newline.

I think there have been enough pointers to where the POSIX spec defines
lines and text files, so no more of that.

But the sh read built-in doesn't actually require either, though it would=

have done back in SUS days (it does still call what is read a =22logical =
line=22
though, but the only definition of that is that it is a single record
read by the read command).

What it is defined to do is to read bytes until it finds the specified
delimiter byte, for which the default is '=5Cn' - and if while doing
that it reaches EOF (ie: the delimiter is not found) the exit status
from read will (eventually) be 1.

Once it has done that, all data read (not counting the delimiter char)
will be split amongst the variables, using approximately the same method
field splitting of variable (etc) expansions uses on command lines
(approximately as read stops splitting once it has made enough fields
for the first N-1 variables (where N is the number of vars given to the
read command) and then just assigns whatever is left (with just leading
and trailing ISP-whitespace deleted) to the N'th (last) variable.
This happens (or should happen) even if read encountered EOF looking
for the delimiter.

Any variables not assigned a value from the field splitting are assigned
=22=22 as their value.

The '=5Cn' character is only relevant if it is the delimiter (or no delim=
iter
is specified, as it is the default), or if -r is not given, and the seque=
nce
backslash-newline appears in the input, in which case if a different deli=
m
wasn't specified, the backslash-newline pair are simply deleted.  If ther=
e
is a different delim, some shells will delete a =5C-delim pair instead of=

a =5C-newline pair (the standard allows both behaviours).   Anything dele=
ted
this way will be deleted before the delimiter check.

kre

ps: if the input to read contains any =5C0 chars, and the delimiter is no=
t =5C0,
then very odd things are likely to happen, =5C0's cannot be stored in she=
ll
vars (which applies to the data read) and since IFS is a shell var, canno=
t
be a field terminator either.   Using =5C0 as the delimiter (-d '') howev=
er
works without issues, though generally you'd always use -r when doing tha=
t.