Unlike your usual grep -r, this implementation
uses breadth-first search. It usually finds
makes it find what you are looking for faster.

Signed-off-by: Mattias Andrée <[email protected]>
---
 grep.1 |  10 ++++--
 grep.c | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
 2 files changed, 121 insertions(+), 10 deletions(-)

diff --git a/grep.1 b/grep.1
index 6f80175..90bcc1f 100644
--- a/grep.1
+++ b/grep.1
@@ -1,4 +1,4 @@
-.Dd 2015-10-08
+.Dd 2016-03-30
 .Dt GREP 1
 .Os sbase
 .Sh NAME
@@ -6,7 +6,7 @@
 .Nd search files for patterns
 .Sh SYNOPSIS
 .Nm
-.Op Fl EFHchilnqsvx
+.Op Fl EFHchilnqrsvx
 .Op Fl e Ar pattern
 .Op Fl f Ar file
 .Op Ar pattern
@@ -55,6 +55,10 @@ Print only the names of files with matching lines.
 Prefix each matching line with its line number in the input.
 .It Fl q
 Print nothing, only return status.
+.It Fl r
+Search directories recursively. If no
+.Ar file
+has been specified, the current working directory is searched.
 .It Fl s
 Suppress the error messages ordinarily written for nonexistent or unreadable
 files.
@@ -89,5 +93,5 @@ utility is compliant with the
 specification.
 .Pp
 The
-.Op Fl Hhw
+.Op Fl Hhrw
 flags are an extension to that specification.
diff --git a/grep.c b/grep.c
index 64ffbe2..fadd661 100644
--- a/grep.c
+++ b/grep.c
@@ -1,4 +1,8 @@
 /* See LICENSE file for copyright and license details. */
+#include <sys/stat.h>
+
+#include <dirent.h>
+#include <errno.h>
 #include <regex.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -21,6 +25,7 @@ static int eflag;
 static int fflag;
 static int hflag;
 static int iflag;
+static int rflag;
 static int sflag;
 static int vflag;
 static int wflag;
@@ -163,10 +168,100 @@ end:
        return match;
 }
 
+static int
+isdir(const char *path)
+{
+       struct stat st;
+       if (stat(path, &st))
+               return 0;
+       return S_ISDIR(st.st_mode);
+}
+
+static int
+grepdir(char *path)
+{
+       const char *path_proper;
+       int m, match = NoMatch;
+       DIR *dir;
+       struct dirent *de;
+       FILE *fp;
+       char *filename;
+       size_t n;
+       struct dir { char *path; TAILQ_ENTRY(dir) entry; } *elem;
+       TAILQ_HEAD(queue, dir) queue = TAILQ_HEAD_INITIALIZER(queue);
+
+       /* FIXME is there a portable way to avoid loops? */
+
+       elem = emalloc(sizeof(*elem));
+       elem->path = estrdup(path);
+       TAILQ_INSERT_TAIL(&queue, elem, entry);
+
+next:
+       elem = TAILQ_FIRST(&queue);
+       path = elem->path;
+       TAILQ_REMOVE(&queue, elem, entry);
+       free(elem);
+
+       path_proper = *path ? path : ".";
+       if (!(dir = opendir(path_proper))) {
+               if (!sflag)
+                       weprintf("opendir %s:", path_proper);
+               return Error;
+       }
+
+       path = erealloc(path, (n = strlen(path)) + 1 + sizeof(de->d_name));
+       filename = path + n;
+       if (n && path[n - 1] != '/')
+               *filename++ = '/';
+
+       while (errno = 0, (de = readdir(dir))) {
+               if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+                       continue;
+
+               strcpy(filename, de->d_name);
+               if (isdir(path)) {
+                       fp = 0;
+               } else if (!(fp = fopen(path, "r"))) {
+                       if (!sflag)
+                               weprintf("fopen %s:", path);
+                       match = Error;
+                       continue;
+               }
+
+               if (fp) {
+                       m = grep(fp, path);
+               } else {
+                       elem = emalloc(sizeof(*elem));
+                       elem->path = estrdup(path);
+                       TAILQ_INSERT_TAIL(&queue, elem, entry);
+                       continue;
+               }
+
+               if (m == Error || (match != Error && m == Match))
+                       match = m;
+               if (fp && fshut(fp, path))
+                       match = Error;
+       }
+
+       free(path);
+
+       if (errno) {
+               if (!sflag)
+                       weprintf("readdir %s:", path_proper);
+               return Error;
+       }
+       closedir(dir);
+
+       if (!TAILQ_EMPTY(&queue))
+               goto next;
+
+       return match;
+}
+
 static void
 usage(void)
 {
-       enprintf(Error, "usage: %s [-EFHchilnqsvwx] [-e pattern] [-f file] "
+       enprintf(Error, "usage: %s [-EFHchilnqrsvwx] [-e pattern] [-f file] "
                 "[pattern] [file ...]\n", argv0);
 }
 
@@ -226,6 +321,9 @@ main(int argc, char *argv[])
                flags |= REG_ICASE;
                iflag = 1;
                break;
+       case 'r':
+               rflag = 1;
+               break;
        case 's':
                sflag = 1;
                break;
@@ -259,24 +357,33 @@ main(int argc, char *argv[])
                /* Compile regex for all search patterns */
                SLIST_FOREACH(pnode, &phead, entry)
                        enregcomp(Error, &pnode->preg, pnode->pattern, flags);
-       many = (argc > 1);
-       if (argc == 0) {
+       many = (argc > 1) || rflag;
+       if (argc == 0 && !rflag) {
                match = grep(stdin, "<stdin>");
        } else {
-               for (; *argv; argc--, argv++) {
+               if (!argc) {
+                       *argv = "";
+                       argc++;
+               }
+               for (; argc; argc--, argv++) {
                        if (!strcmp(*argv, "-")) {
                                *argv = "<stdin>";
                                fp = stdin;
+                       } else if (rflag && (!**argv || isdir(*argv))) {
+                               fp = 0;
                        } else if (!(fp = fopen(*argv, "r"))) {
                                if (!sflag)
-                                       weprintf("fopen %s:", *argv);
+                                       weprintf("fopen %s, %i:", *argv);
                                match = Error;
                                continue;
                        }
-                       m = grep(fp, *argv);
+                       if (fp)
+                               m = grep(fp, *argv);
+                       else
+                               m = grepdir(*argv);
                        if (m == Error || (match != Error && m == Match))
                                match = m;
-                       if (fp != stdin && fshut(fp, *argv))
+                       if (fp && fp != stdin && fshut(fp, *argv))
                                match = Error;
                }
        }
-- 
2.7.4


Reply via email to