diff --git a/doc/grep.in.1 b/doc/grep.in.1 index ecc8105..ea13964 100644 --- a/doc/grep.in.1 +++ b/doc/grep.in.1 @@ -472,6 +472,30 @@ or to set in the environment, in order to find more matches even if the matches are unsafe for direct display. .TP +.BI \-p " COMMAND" "\fR,\fP \-\^\-preprocess=" COMMAND +If this option is given, for each input file +.I COMMAND +is invoked with that input file as standard input and with grep reading +and searching the standard output of +.I COMMAND +instead of direct file contents. E.g., if +.I COMMAND +is +.RB "\*(lq" "gzip -dc" "\*(rq" +grep will decompress each file before searching it, assuming all +inputs are gzipped. +.I COMMAND +is run via \fB${GREP_SHELL:-/bin/sh} -c COMMAND\fR. +.IP +The environment variable GREP_INPUT set to each input filename. +This may be useful to dispatch to appropriate decoders. +E.g., with a standard shell, (note single/double quoting), +.nf + GPP='case "$GREP_INPUT" in *.gz) gzip -dc;; *) cat;; esac' +.fi +will allow \fB grep -p"$GPP"\fR to search through a mixture of +gzipped and ordinary files. +.TP .BI \-D " ACTION" "\fR,\fP \-\^\-devices=" ACTION If an input file is a device, FIFO or socket, use .I ACTION diff --git a/src/grep.c b/src/grep.c index bc47243..c629b13 100644 --- a/src/grep.c +++ b/src/grep.c @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include #include #include @@ -415,7 +417,7 @@ static struct exclude *excluded_patterns[2]; static struct exclude *excluded_directory_patterns[2]; /* Short options. */ static char const short_options[] = -"0123456789A:B:C:D:EFGHIPTUVX:abcd:e:f:hiLlm:noqRrsuvwxyZz"; +"0123456789A:B:C:D:EFGHIPTUVX:abcd:e:f:hiLlm:noqRrsuvwxyZzp:"; /* Non-boolean long options that have no corresponding short equivalents. */ enum @@ -484,6 +486,7 @@ static struct option const long_options[] = {"version", no_argument, NULL, 'V'}, {"with-filename", no_argument, NULL, 'H'}, {"word-regexp", no_argument, NULL, 'w'}, + {"preprocess", required_argument, NULL, 'p'}, {0, 0, 0, 0} }; @@ -535,6 +538,9 @@ static enum SKIP_DEVICES } devices = READ_COMMAND_LINE_DEVICES; +static char *preproc; /* if non-NULL pipe each file input to this command */ +static pid_t preprocPID; /* current preproc decoder child PID to wait for. */ + static bool grepfile (int, char const *, bool, bool); static bool grepdesc (int, bool); @@ -1848,6 +1854,45 @@ grepdesc (int desc, bool command_line) goto closeout; } + if (preproc) /* desc <- preprocessing decoder output */ + { + int fds[2], ac = 0; + char *sh, *av[5 + 3]; + if (pipe (fds) == -1) + die (EXIT_TROUBLE, 0, _("cannot create a pipe")); + switch (preprocPID = vfork ()) + { + case 0: /* child */ + dup2 (desc, 0); /* stdin = desc on file itself */ + dup2 (fds[1], 1); /* stdout = write end of pipe [1] */ + close (fds[1]); /* do not need extra handle on pipe */ + close (fds[0]); /* close read end of pipe [0] */ + setenv ("GREP_INPUT", filename, 1); + if (!(sh = getenv ("GREP_SHELL"))) + sh = (char *)"/bin/sh"; + /* build the argument vector for execvp */ + ac = 0; + /* word-split GREP_SHELL a few times in case user needs option args */ + av[ac] = strtok (sh, " \t"); + for (ac++; ac < 5; ac++) + if (!(av[ac] = strtok (NULL, " \t"))) + break; + /* append -c preproc */ + av[ac++] = (char *) "-c"; + av[ac++] = preproc; + av[ac] = NULL; + execvp (av[0], av); /* become a preprocessing decoder */ + fprintf (stderr, "%s -c \"%s\": %s\n", sh, preproc, strerror (errno)); + _exit (EXIT_TROUBLE); + default: /* parent */ + close (fds[1]); /* close write end of pipe[1] */ + close (desc); /* close desc on file itself */ + desc = fds[0]; /* replace desc value with read end of pipe [0] */ + break; + case -1: /* vfork failed => resource exhaustion => die */ + die (EXIT_TROUBLE, 0, _("cannot vfork")); + } + } count = grep (desc, &st, &ineof); if (count_matches) { @@ -1876,6 +1921,21 @@ grepdesc (int desc, bool command_line) fflush_errno (); } + if (preprocPID) + { + int wstatus = 0; + /* Do not decode whole file if it won't be read anyway, e.g. -l mode. */ + kill (preprocPID, SIGKILL); /* waitpid NOHANG before killing? */ + while (waitpid (preprocPID, &wstatus, 0) == -1) + if (errno != EINTR) + break; + /* post-kill status always == success; Kid could fail sooner, though. */ + if (WIFEXITED(wstatus) && WEXITSTATUS(wstatus) != EXIT_SUCCESS) + die (EXIT_TROUBLE, 0, _("preprocess command failed")); + preprocPID = 0; + /*XXX verify that signal was SIGKILL and perhaps die otherwise? */ + } + closeout: if (desc != STDIN_FILENO && close (desc) != 0) suppressible_error (errno); @@ -1958,6 +2018,7 @@ Output control:\n\ ")); printf (_("\ -I equivalent to --binary-files=without-match\n\ + -p, --preprocess=COMMAND pipe file inputs to stdin of COMMAND pre-search\n\ -d, --directories=ACTION how to handle directories;\n\ ACTION is 'read', 'recurse', or 'skip'\n\ -D, --devices=ACTION how to handle devices, FIFOs and sockets;\n\ @@ -2678,6 +2739,10 @@ main (int argc, char **argv) eolbyte = '\0'; break; + case 'p': + preproc = optarg; + break; + case BINARY_FILES_OPTION: if (STREQ (optarg, "binary")) binary_files = BINARY_BINARY_FILES;