Subject: bug#26254: grep's -m breaks -A

From: Paul Eggert <[email-masked]>
Date: Wed, 21 Jun 2017 14:10:29 -0700

On 06/17/2017 08:24 PM, Paul Eggert wrote:
>
> This would also change the meaning of grep -v --max-count=NUM

After looking into this, my proposal was too drastic. So I am attaching 
a more modest patch that would simply change grep's behavior along the 
lines requested. Athough this is an incompatible change, I think almost 
all users who care about it will prefer the new behavior. If there is a 
problem with it I suppose we could have an option to select the old 
behavior, but I hope it doesn't come to that.

>From 2ee0422feeec5a324efca117264320e562e255ac Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email-masked]>
Date: Wed, 21 Jun 2017 13:59:57 -0700
Subject: [PATCH] grep: -m no longer cuts off trailing context

* NEWS, doc/grep.texi (General Output Control): Document this.
* src/grep.c (prpending): Selected lines no longer cut off context.
(usage): Say "selected" instead of "matching", where appropriate.
* tests/foad1, tests/max-count-vs-context, tests/yesno:
Adjust to match new behavior.
---
 NEWS                       |  5 +++++
 doc/grep.texi              | 10 ++++------
 src/grep.c                 | 29 ++++++++++-------------------
 tests/foad1                |  8 ++++----
 tests/max-count-vs-context |  2 +-
 tests/yesno                |  8 ++++----
 6 files changed, 28 insertions(+), 34 deletions(-)

diff --git a/NEWS b/NEWS
index 0a8cbbc..bd2c00a 100644
--- a/NEWS
+++ b/NEWS
@@ -9,6 +9,11 @@ GNU grep NEWS                                    -*- outline -*-
 
 ** Changes in behavior
 
+  Context no longer excludes selected lines omitted because of -m.
+  For example, 'grep "^" -m1 -A1' now outputs the first two input
+  lines, not just the first line.  This fixes a glitch that has been
+  present since -m was added in grep 2.5.
+
   The following changes affect only MS-Windows platforms.  First, the
   --binary (-U) option now governs whether binary I/O is used, instead
   of a heuristic that was sometimes incorrect.  Second, the
diff --git a/doc/grep.texi b/doc/grep.texi
index 728c039..334a059 100644
--- a/doc/grep.texi
+++ b/doc/grep.texi
@@ -311,11 +311,11 @@ The scanning of each file stops on the first match.
 @opindex -m
 @opindex --max-count
 @cindex max-count
-Stop reading a file after @var{num} matching lines.
+Stop after the first @var{num} selected lines.
 If the input is standard input from a regular file,
-and @var{num} matching lines are output,
+and @var{num} selected lines are output,
 @command{grep} ensures that the standard input is positioned
-just after the last matching line before exiting,
+just after the last selected line before exiting,
 regardless of the presence of trailing context lines.
 This enables a calling process to resume a search.
 For example, the following shell script makes use of it:
@@ -340,10 +340,8 @@ done
 @end example
 
 @cindex context lines
-When @command{grep} stops after @var{num} matching lines,
+When @command{grep} stops after @var{num} selected lines,
 it outputs any trailing context lines.
-Since context does not include matching lines,
-@command{grep} will stop when it encounters another matching line.
 When the @option{-c} or @option{--count} option is also used,
 @command{grep} does not output a count greater than @var{num}.
 When the @option{-v} or @option{--invert-match} option is also used,
diff --git a/src/grep.c b/src/grep.c
index bc2599f..8d22aec 100644
--- a/src/grep.c
+++ b/src/grep.c
@@ -1011,7 +1011,7 @@ static intmax_t out_before;	/* Lines of leading context. */
 static intmax_t out_after;	/* Lines of trailing context. */
 static bool count_matches;	/* Count matching lines.  */
 static bool no_filenames;	/* Suppress file names.  */
-static intmax_t max_count;	/* Stop after outputting this many
+static intmax_t max_count;	/* Max number of selected
                                    lines from an input file.  */
 static bool line_buffered;	/* Use line buffering.  */
 static char *label = NULL;      /* Fake filename for stdin */
@@ -1023,7 +1023,7 @@ static char const *lastnl;	/* Pointer after last newline counted. */
 static char *lastout;		/* Pointer after last character output;
                                    NULL if no character has been output
                                    or if it's conceptually before bufbeg. */
-static intmax_t outleft;	/* Maximum number of lines to be output.  */
+static intmax_t outleft;	/* Maximum number of selected lines.  */
 static intmax_t pending;	/* Pending lines of output.
                                    Always kept 0 if out_quiet is true.  */
 static bool done_on_match;	/* Stop scanning file on first match.  */
@@ -1276,25 +1276,16 @@ prline (char *beg, char *lim, char sep)
   lastout = lim;
 }
 
-/* Print pending lines of trailing context prior to LIM. Trailing context ends
-   at the next matching line when OUTLEFT is 0.  */
+/* Print pending lines of trailing context prior to LIM.  */
 static void
 prpending (char const *lim)
 {
   if (!lastout)
     lastout = bufbeg;
-  while (pending > 0 && lastout < lim)
+  for (; 0 < pending && lastout < lim; pending--)
     {
       char *nl = memchr (lastout, eolbyte, lim - lastout);
-      size_t match_size;
-      --pending;
-      if (outleft
-          || ((execute (compiled_pattern, lastout, nl + 1 - lastout,
-                        &match_size, NULL) == (size_t) -1)
-              == !out_invert))
-        prline (lastout, nl + 1, SEP_CHAR_REJECTED);
-      else
-        pending = 0;
+      prline (lastout, nl + 1, SEP_CHAR_REJECTED);
     }
 }
 
@@ -1949,11 +1940,11 @@ Miscellaneous:\n\
       printf (_("\
 \n\
 Output control:\n\
-  -m, --max-count=NUM       stop after NUM matches\n\
+  -m, --max-count=NUM       stop after NUM selected lines\n\
   -b, --byte-offset         print the byte offset with output lines\n\
   -n, --line-number         print line number with output lines\n\
       --line-buffered       flush output on every line\n\
-  -H, --with-filename       print the file name for each match\n\
+  -H, --with-filename       print file name with output lines\n\
   -h, --no-filename         suppress the file name prefix on output\n\
       --label=LABEL         use LABEL as the standard input file name prefix\n\
 "));
@@ -1981,9 +1972,9 @@ Output control:\n\
       --exclude-dir=PATTERN  directories that match PATTERN will be skipped.\n\
 "));
       printf (_("\
-  -L, --files-without-match  print only names of FILEs containing no match\n\
-  -l, --files-with-matches  print only names of FILEs containing matches\n\
-  -c, --count               print only a count of matching lines per FILE\n\
+  -L, --files-without-match  print only names of FILEs with no selected lines\n\
+  -l, --files-with-matches  print only names of FILEs with selected lines\n\
+  -c, --count               print only a count of selected lines per FILE\n\
   -T, --initial-tab         make tabs line up (if needed)\n\
   -Z, --null                print 0 byte after FILE name\n"));
       printf (_("\
diff --git a/tests/foad1 b/tests/foad1
index 0163f1a..e399981 100755
--- a/tests/foad1
+++ b/tests/foad1
@@ -93,12 +93,12 @@ grep_test "wordword/" "${CB}word${CE}word/" "\<word" --color=always
 grep_test "4/40/"  "4/40/"  "^4$" -m1 -A99
 grep_test "4/04/"  "4/04/"  "^4$" -m1 -A99
 grep_test "4/444/" "4/444/" "^4$" -m1 -A99
-grep_test "4/40/"  "4/"     "^4"  -m1 -A99
+grep_test "4/40/"  "4/40/"  "^4"  -m1 -A99
 grep_test "4/04/"  "4/04/"  "^4"  -m1 -A99
-grep_test "4/444/" "4/"     "^4"  -m1 -A99
+grep_test "4/444/" "4/444/" "^4"  -m1 -A99
 grep_test "4/40/"  "4/40/"  "4$"  -m1 -A99
-grep_test "4/04/"  "4/"     "4$"  -m1 -A99
-grep_test "4/444/" "4/"     "4$"  -m1 -A99
+grep_test "4/04/"  "4/04/"  "4$"  -m1 -A99
+grep_test "4/444/" "4/444/" "4$"  -m1 -A99

 # Test for "-F -w" bugs.  Thanks to Gordon Lack for these two.
diff --git a/tests/max-count-vs-context b/tests/max-count-vs-context
index 5633dd6..e80c3ce 100755
--- a/tests/max-count-vs-context
+++ b/tests/max-count-vs-context
@@ -12,7 +12,7 @@ another needle
 6th line...
 EOF
 
-sed 4q in > exp || framework_failure_
+sed 6q in > exp || framework_failure_
 
 fail=0
 grep -m1 -A5 needle in > out 2>err || fail=1
diff --git a/tests/yesno b/tests/yesno
index e379c19..1ae2142 100755
--- a/tests/yesno
+++ b/tests/yesno
@@ -73,14 +73,14 @@ set x \
   '-C,1,--no-gr'    "$rB$C$D$E$rF$rG$H$I$rJ$rL$M$N$z0" \
   '-m,4'            "$C$D$E$H$z0$XI$XJ$XK$XL$XM$XN" \
   '-m,4,-o'         "$c$d$e$h$z0$XI$XJ$XK$XL$XM$XN" \
-  '-m,4,-C,1'       "$rB$C$D$E$rF$rG$H$z0$XI$XJ$XK$XL$XM$XN" \
+  '-m,4,-C,1'       "$rB$C$D$E$rF$rG$H$rI$z0$XI$XJ$XK$XL$XM$XN" \
   '-m,4,-C,1,-o'    "$c$d$e$h$z0$XI$XJ$XK$XL$XM$XN" \
   '-m,5'            "$C$D$E$H$I$z0$XJ$XK$XL$XM$XN" \
   '-m,5,-o'         "$c$d$e$h$i$z0$XJ$XK$XL$XM$XN" \
   '-m,5,-C,1,-o'    "$c$d$e$h$i$z0$XJ$XK$XL$XM$XN" \
   '-m,6'            "$C$D$E$H$I$M$z0$XN" \
   '-m,6,-o'         "$c$d$e$h$i$m$z0$XN" \
-  '-m,6,-C,1'       "$rB$C$D$E$rF$rG$H$I$rJ$s$rL$M$z0$XN" \
+  '-m,6,-C,1'       "$rB$C$D$E$rF$rG$H$I$rJ$s$rL$M$rN$z0$XN" \
   '-m,6,-C,1,-o'    "$c$d$e$h$i$s$m$z0$XN" \
   '-v'              "$A$B$F$G$J$K$L$z0" \
   '-v,-o'           "$z0" \
@@ -92,13 +92,13 @@ set x \
   '-4,-v,-1'        "$A$B$rC$s$rE$F$G$rH$rI$J$K$L$rM$z0" \
   '-m,1,-v'         "$A$z0$XB$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,1,-v,-o'      "$z0$XB$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
-  '-m,1,-v,-C,1'    "$A$z0$XB$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
+  '-m,1,-v,-C,1'    "$A$rB$z0$XB$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,1,-v,-C,1,-o' "$z0$XB$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,2,-v'         "$A$B$z0$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,2,-v,-o'      "$z0$XC$XD$XE$XF$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,3,-v'         "$A$B$F$z0$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,3,-v,-o'      "$z0$XG$XH$XI$XJ$XK$XL$XM$XN" \
-  '-m,3,-v,-C,1'    "$A$B$rC$s$rE$F$z0$XG$XH$XI$XJ$XK$XL$XM$XN" \
+  '-m,3,-v,-C,1'    "$A$B$rC$s$rE$F$rG$z0$XG$XH$XI$XJ$XK$XL$XM$XN" \
   '-m,3,-v,-C,1,-o' "$rc$s$re$z0$XG$XH$XI$XJ$XK$XL$XM$XN" \
   x
 shift
-- 
2.9.4