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:
 

Reply via email to