zgrep: with -f SPECIAL, read SPECIAL just once
authorPaul Eggert <eggert@cs.ucla.edu>
Fri, 18 Mar 2016 22:20:53 +0000 (15:20 -0700)
committerPaul Eggert <eggert@cs.ucla.edu>
Fri, 18 Mar 2016 22:27:41 +0000 (15:27 -0700)
Problem reported by Fulvio Scapin in: http://bugs.gnu.org/22945
* NEWS: Document this.
* tests/zgrep-f: Add a test.
Adjust a test to cover the case of more than one line in -f's input.
* zgrep.in (with_filename): With -f FILE, if FILE is stdin or not
a regular file, copy it into a temporary and use the temporary.

NEWS
tests/zgrep-f
zgrep.in

diff --git a/NEWS b/NEWS
index 6363d71bdb9f50432ce4c093ac9682cc0b13d3f3..d60d5cc6c60c445d54048f23ff79f9fff08479cd 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -35,6 +35,10 @@ GNU gzip NEWS                                    -*- outline -*-
   gzip -k -v no longer reports that files are replaced.
   [bug present since the beginning]
 
+  zgrep -f A B C no longer reads A more than once if A is not a regular file.
+  This better supports invocations like 'zgrep -f <(COMMAND) B C' in Bash.
+  [bug introduced in gzip-1.2]
+
 
 * Noteworthy changes in release 1.6 (2013-06-09) [stable]
 
index a8eb746f692d7f9c0816f5fff5eb1e12130cdc74..1ce8cc293f260659396d71b39a25c492979fe96f 100755 (executable)
@@ -20,8 +20,8 @@
 
 . "${srcdir=.}/init.sh"; path_prepend_ ..
 
-echo needle > n || framework_failure_
-echo needle > haystack || framework_failure_
+printf 'needle\nn2\n' > n || framework_failure_
+cp n haystack || framework_failure_
 gzip haystack || framework_failure_
 
 fail=0
@@ -29,6 +29,14 @@ zgrep -f - haystack.gz < n > out 2>&1 || fail=1
 
 compare out n || fail=1
 
+if ${BASH_VERSION+:} false; then
+  set +o posix
+  # This failed with gzip 1.6.
+  cat n n >nn || framework_failure_
+  eval 'zgrep -h -f <(cat n) haystack.gz haystack.gz' >out || fail=1
+  compare out nn || fail=1
+fi
+
 # This failed with gzip 1.4.
 echo a-b | zgrep -e - > /dev/null || fail=1
 
index c24be57a4318a3dea22cfb6e6840e9bca84abb5e..99ace593ce1560b6ceff15d7a09090ed07082d0d 100644 (file)
--- a/zgrep.in
+++ b/zgrep.in
@@ -55,6 +55,7 @@ files_with_matches=0
 files_without_matches=0
 no_filename=0
 with_filename=0
+pattmp=
 
 while test $# -ne 0; do
   option=$1
@@ -113,13 +114,34 @@ while test $# -ne 0; do
     # The pattern is coming from a file rather than the command-line.
     # If the file is actually stdin then we need to do a little
     # magic, since we use stdin to pass the gzip output to grep.
-    # Turn the -f option into an -e option by copying the file's
-    # contents into OPTARG.
-    case $optarg in
-    (" '-'" | " '/dev/stdin'" | " '/dev/fd/0'")
-      option=-e
-      optarg=" '"$(sed "$escape") || exit 2;;
-    esac
+    # Similarly if it is not a regular file, since it might be read repeatedly.
+    # In either of these two cases, copy the pattern into a temporary file,
+    # and use that file instead.  The pattern might contain null bytes,
+    # so we cannot simply switch to -e here.
+    if case $optarg in
+       (" '-'" | " '/dev/stdin'" | " '/dev/fd/0'")
+         :;;
+       (*)
+         eval "test ! -f$optarg";;
+       esac
+    then
+      if test -n "$pattmp"; then
+        eval "cat --$optarg" >>"$pattmp" || exit 2
+        continue
+      fi
+      trap '
+        test -n "$pattmp" && rm -f "$pattmp"
+        (exit 2); exit 2
+      ' HUP INT PIPE TERM 0
+      if type mktemp >/dev/null 2>&1; then
+        pattmp=$(mktemp -t -- "zgrep.XXXXXX") || exit 2
+      else
+        set -C
+        pattmp=${TMPDIR-/tmp}/zgrep.$$
+      fi
+      eval "cat --$optarg" >"$pattmp" || exit 2
+      optarg=' "$pattmp"'
+    fi
     have_pat=1;;
   (--h | --he | --hel | --help)
     echo "$usage" || exit 2
@@ -232,5 +254,14 @@ do
   test 126 -le $res && break
 done
 
+if test -n "$pattmp"; then
+  rm -f "$pattmp" || {
+    r=$?
+    test $r -lt 2 && r=2
+    test $res -lt $r && res=$r
+  }
+  trap - HUP INT PIPE TERM 0
+fi
+
 test 128 -le $res && kill -$(expr $res % 128) $$
 exit $res