Subject: bug#70540: grep -c -r | grep -v ':0$'

From: "Dale R. Worley" <[email-masked]>
Date: Sun, 28 Apr 2024 18:44:45 -0400

For everyone's critique, here are my changes (to grep 3.11):

diff -u doc/grep.in.1.orig doc/grep.in.1
--- doc/grep.in.1.orig	2024-04-28 18:04:37.494096472 -0400
+++ doc/grep.in.1	2024-04-28 18:24:15.187984393 -0400
@@ -2,7 +2,7 @@
 .de dT
 .ds Dt \\$2
 ..
-.dT Time-stamp: "2019-12-29"
+.dT Time-stamp: "2024-04-28"
 .\" Update the above date whenever a change to either this file or
 .\" grep.c's 'usage' function results in a nontrivial change to the man page.
 .\" In Emacs, you can update the date by running 'M-x time-stamp'
@@ -292,6 +292,9 @@
 With the
 .BR \-v ", " \-\^\-invert\-match
 option (see above), count non-matching lines.
+With the
+.BR \-l ", " \-\^\-files\-with\-matches
+option (see below), only files with non-zero counts are listed.
 .TP
 .BR \-\^\-color [ =\fIWHEN\fP "], " \-\^\-colour [ =\fIWHEN\fP ]
 Surround the matched (non-empty) strings, matching lines, context lines,
diff -u doc/grep.texi.orig doc/grep.texi
--- doc/grep.texi.orig	2024-04-28 18:04:40.302091195 -0400
+++ doc/grep.texi	2024-04-28 18:17:22.567712036 -0400
@@ -301,6 +301,10 @@
 With the @option{-v} (@option{--invert-match}) option,
 count non-matching lines.
 (@option{-c} is specified by POSIX.)
+With the @option{-l} (@option{--files-with-matches}) option,
+only files with non-zero counts are listed.
+(The combination of @option{-c} and @option{-l} is not specified by
+POSIX.)
 
 @item --color[=@var{WHEN}]
 @itemx --colour[=@var{WHEN}]
diff -u src/grep.c.orig src/grep.c
--- src/grep.c.orig	2023-04-10 20:20:47.000000000 -0400
+++ src/grep.c	2024-04-28 17:57:22.527913936 -0400
@@ -1084,6 +1084,8 @@
 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 count_matches_nonzero; /* Count matching lines; only
+				      report files with matches.  */
 static intmax_t max_count;	/* Max number of selected
                                    lines from an input file.  */
 static bool line_buffered;	/* Use line buffering.  */
@@ -1914,17 +1916,20 @@
   count = grep (desc, &st, &ineof);
   if (count_matches)
     {
-      if (out_file)
-        {
-          print_filename ();
-          if (filename_mask)
-            print_sep (SEP_CHAR_SELECTED);
-          else
-            putchar_errno (0);
-        }
-      printf_errno ("%" PRIdMAX "\n", count);
-      if (line_buffered)
-        fflush_errno ();
+      if (!(count_matches_nonzero && count == 0))
+	{
+	  if (out_file)
+	    {
+	      print_filename ();
+	      if (filename_mask)
+		print_sep (SEP_CHAR_SELECTED);
+	      else
+		putchar_errno (0);
+	    }
+	  printf_errno ("%" PRIdMAX "\n", count);
+	  if (line_buffered)
+	    fflush_errno ();
+	}
     }
 
   status = !count;
@@ -2891,9 +2896,16 @@
     }
 
   /* POSIX says -c, -l and -q are mutually exclusive.  In this
-     implementation, -q overrides -l and -L, which in turn override -c.  */
+     implementation, -q overrides -l and -L.  -L in turn overrides -c,
+     but -l is compatible with -c because this implementation uses
+     that combination to specify listing only non-zero counts.  */
   if (exit_on_match | dev_null_output)
     list_files = LISTFILES_NONE;
+  if (count_matches && list_files == LISTFILES_MATCHING)
+    {
+      count_matches_nonzero = true;
+      list_files = LISTFILES_NONE;
+    }
   if ((exit_on_match | dev_null_output) || list_files != LISTFILES_NONE)
     {
       count_matches = false;
diff -u tests/in-eq-out-infloop.orig tests/in-eq-out-infloop
--- tests/in-eq-out-infloop.orig	2024-04-28 18:32:29.435077799 -0400
+++ tests/in-eq-out-infloop	2024-04-28 17:56:59.254957675 -0400
@@ -29,7 +29,7 @@
   compare err.exp err || fail=1
 
   # But with each of the following options it must not exit-2.
-  for i in -q -m1 -l -L; do
+  for i in -q -m1 -l -L -c; do
     timeout 10 grep $i 0 $arg < out >> out 2> err; st=$?
     test $st = 2 && fail=1
   done
diff -u tests/options.orig tests/options
--- tests/options.orig	2024-04-28 18:33:47.645931404 -0400
+++ tests/options	2024-04-28 18:00:23.701573443 -0400
@@ -12,6 +12,9 @@
 # grep [ -E| -F][ -c| -l| -q ][-insvx][-e pattern_list]
 #      -f pattern_file ... [file ...]
 # grep [ -E| -F][ -c| -l| -q ][-insvx] pattern_list [file...]
+#
+# Also checks that the option combination "-c -l" only reports files
+# with non-zero counts.
 
 . "${srcdir=.}/init.sh"; path_prepend_ ../src
 
@@ -46,4 +49,63 @@
         fail=1
 fi
 
+# check the option combination -c -l
+echo 'This file contains foo.' > options.in.foo
+echo 'This file contains bar.' > options.in.bar
+
+# check without options
+output=$( grep foo options.in.* > /dev/null 2>&1 )
+if test $? -ne 0 ; then
+        echo "Options: Wrong status code, test #5a failed"
+        fail=1
+fi
+if test "$output" -ne "options.in.foo:This file contains foo." ; then
+        echo "Options: Wrong output, test #5a failed: $output"
+        fail=1
+fi
+
+# check with -c
+output=$( grep -c foo options.in.* > /dev/null 2>&1 )
+if test $? -ne 0 ; then
+        echo "Options: Wrong status code, test #5b failed"
+        fail=1
+fi
+if test "$output" -ne "options.in.foo:1 options.in.bar:0" ; then
+        echo "Options: Wrong output, test #5b failed: $output"
+        fail=1
+fi
+
+# check with -l
+output=$( grep -l foo options.in.* > /dev/null 2>&1 )
+if test $? -ne 0 ; then
+        echo "Options: Wrong status code, test #5c failed"
+        fail=1
+fi
+if test "$output" -ne "options.in.foo" ; then
+        echo "Options: Wrong output, test #5c failed: $output"
+        fail=1
+fi
+
+# check with -c -l
+output=$( grep -c -l foo options.in.* > /dev/null 2>&1 )
+if test $? -ne 0 ; then
+        echo "Options: Wrong status code, test #5d failed"
+        fail=1
+fi
+if test "$output" -ne "options.in.foo:1" ; then
+        echo "Options: Wrong output, test #5d failed: $output"
+        fail=1
+fi
+
+# check with -v -c -l
+output=$( grep -v -c -l foo options.in.* > /dev/null 2>&1 )
+if test $? -ne 0 ; then
+        echo "Options: Wrong status code, test #5e failed"
+        fail=1
+fi
+if test "$output" -ne "options.in.bar:1" ; then
+        echo "Options: Wrong output, test #5e failed: $output"
+        fail=1
+fi
+
 Exit $fail