Hello,
my apologies if there's a much easier solution for the following problem - in
this case please let me know!
>From time to time a run into troubles when reading a file with a while-read
>loop where the last "line" is not terminated with a newline.
I found an ugly looking solution (probably relying on undocumented features)
when reading the whole line into one variable (see below).
The attached patch for bash-5.1.8 will add an -E option to the builtin read
that will avoid the problem.
To test it run the patched bash on the following script:
>>>
input=$'Line 1\nLine 2\nIncomplete line 3'
echo "while read line"
printf '%s' "$input" | while read line; do printf ' %s\n' "$line"; done
echo "while read line || [[ \$line != '' ]]"
printf '%s' "$input" | while read line || [[ $line != '' ]]; do printf ' %s\n'
"$line"; done
echo "while read -E line"
printf '%s' "$input" | while read -E line; do printf ' %s\n' "$line"; done
echo "while read -E line with no characters between last \\n and EOF"
printf '%s\n' "$input" | sed 's/Incomplete l/L/' | while read -E line; do
printf ' %s\n' "$line"; done
<<<
The patch has not been tested intensely - first I would like to hear if I'm on
a sensible way.
Best regards
Martin
--- ../bash-5.1.8-ori/builtins/read.def 2020-06-05 19:18:28.000000000 +0200
+++ builtins/read.def 2021-10-23 21:23:37.067915781 +0200
@@ -22,7 +22,7 @@
$BUILTIN read
$FUNCTION read_builtin
-$SHORT_DOC read [-ers] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
+$SHORT_DOC read [-eErs] [-a array] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name ...]
Read a line from the standard input and split it into fields.
Reads a single line from the standard input, or from file descriptor FD
@@ -40,6 +40,7 @@
-d delim continue until the first character of DELIM is read, rather
than newline
-e use Readline to obtain the line
+ -E return text between last newline and EOF
-i text use TEXT as the initial text for Readline
-n nchars return after reading NCHARS characters rather than waiting
for a newline, but honor a delimiter if fewer than
@@ -179,7 +180,7 @@
int size, nr, pass_next, saw_escape, eof, opt, retval, code, print_ps2, nflag;
volatile int i;
int input_is_tty, input_is_pipe, unbuffered_read, skip_ctlesc, skip_ctlnul;
- int raw, edit, nchars, silent, have_timeout, ignore_delim, fd;
+ int raw, edit, eof_terminates_line, nchars, silent, have_timeout, ignore_delim, fd;
int lastsig, t_errno;
int mb_cur_max;
unsigned int tmsec, tmusec;
@@ -209,6 +210,7 @@
USE_VAR(input_is_pipe);
/* USE_VAR(raw); */
USE_VAR(edit);
+ USE_VAR(eof_terminates_line);
USE_VAR(tmsec);
USE_VAR(tmusec);
USE_VAR(nchars);
@@ -229,6 +231,7 @@
i = 0; /* Index into the string that we are reading. */
raw = edit = 0; /* Not reading raw input by default. */
+ eof_terminates_line = 0;
silent = 0;
arrayname = prompt = (char *)NULL;
fd = 0; /* file descriptor to read from */
@@ -245,7 +248,7 @@
ignore_delim = nflag = 0;
reset_internal_getopt ();
- while ((opt = internal_getopt (list, "ersa:d:i:n:p:t:u:N:")) != -1)
+ while ((opt = internal_getopt (list, "eErsa:d:i:n:p:t:u:N:")) != -1)
{
switch (opt)
{
@@ -263,6 +266,9 @@
edit = 1;
#endif
break;
+ case 'E':
+ eof_terminates_line = 1;
+ break;
case 'i':
#if defined (READLINE)
itext = list_optarg;
@@ -788,7 +794,14 @@
discard_unwind_frame ("read_builtin");
- retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+ if (!eof_terminates_line)
+ {
+ retval = eof ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+ }
+ else
+ {
+ retval = eof && strlen (input_string) == 0 ? EXECUTION_FAILURE : EXECUTION_SUCCESS;
+ }
assign_vars: