gopkg.in/hugelgupf/u-root.v7@v7.0.0-20180831062900-6a07824681b2/cmds/grep/grep.go (about)

     1  // Copyright 2012-2017 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Concurrent, parallel grep.
     6  //
     7  // Synopsis:
     8  //     grep [-vrlq] [FILE]...
     9  //
    10  // Description:
    11  //     It has to deal with the EMFILE limit. To do so we have one chan that is
    12  //     bounded. From args, we use filepath.Walk to generate a chan of names.
    13  //     From that, we create a chan of grepCommands. From that, we create a chan
    14  //     of grepResults. The grepResults contain matches or not-matches only. If
    15  //     we are in -l mode, the goprocs handling the grep bail out as soon as the
    16  //     condition is met. This grep is about 2x faster than GNU grep for simple
    17  //     non-recursive greps and slower as soon as filepath. Walk enters the
    18  //     picture. Let's fix this.
    19  //
    20  // Options:
    21  //     -v: print only non-matching lines
    22  //     -r: recursive
    23  //     -l: list only files
    24  //     -q: don't print matches; exit on first match
    25  package main
    26  
    27  import (
    28  	"bufio"
    29  	"flag"
    30  	"fmt"
    31  	"os"
    32  	"path/filepath"
    33  	"regexp"
    34  )
    35  
    36  type grepResult struct {
    37  	match bool
    38  	c     *grepCommand
    39  	line  *string
    40  }
    41  
    42  type grepCommand struct {
    43  	name string
    44  	*os.File
    45  }
    46  
    47  type oneGrep struct {
    48  	c chan *grepResult
    49  }
    50  
    51  var (
    52  	match       = flag.Bool("v", true, "Print only non-matching lines")
    53  	recursive   = flag.Bool("r", false, "recursive")
    54  	noshowmatch = flag.Bool("l", false, "list only files")
    55  	showname    = false
    56  	quiet       = flag.Bool("q", false, "Don't print matches; exit on first match")
    57  	allGrep     = make(chan *oneGrep)
    58  	nGrep       = 0
    59  )
    60  
    61  // grep reads data from the os.File embedded in grepCommand.
    62  // It creates a chan of grepResults and pushes a pointer to it into allGrep.
    63  // It matches each line against the re and pushes the matching result
    64  // into the chan.
    65  // Bug: this chan should be created by the caller and passed in
    66  // to preserve file name order. Oops.
    67  // If we are only looking for a match, we exit as soon as the condition is met.
    68  // "match" means result of re.Match == match flag.
    69  func grep(f *grepCommand, re *regexp.Regexp) {
    70  	nGrep++
    71  	r := bufio.NewReader(f)
    72  	res := make(chan *grepResult, 1)
    73  	allGrep <- &oneGrep{res}
    74  	for {
    75  		if i, err := r.ReadString('\n'); err == nil {
    76  			m := re.Match([]byte(i))
    77  			if m == *match {
    78  				res <- &grepResult{re.Match([]byte(i)), f, &i}
    79  				if *noshowmatch {
    80  					break
    81  				}
    82  			}
    83  		} else {
    84  			break
    85  		}
    86  	}
    87  	close(res)
    88  	f.Close()
    89  }
    90  
    91  func printmatch(r *grepResult) {
    92  	var prefix string
    93  	if showname {
    94  		fmt.Printf("%v", r.c.name)
    95  		prefix = ":"
    96  	}
    97  	if *noshowmatch {
    98  		return
    99  	}
   100  	if r.match == *match {
   101  		fmt.Printf("%v%v", prefix, *r.line)
   102  	}
   103  }
   104  
   105  func main() {
   106  	r := ".*"
   107  	flag.Parse()
   108  	a := flag.Args()
   109  	if len(a) > 0 {
   110  		r = a[0]
   111  	}
   112  	re := regexp.MustCompile(r)
   113  	// very special case, just stdin ...
   114  	if len(a) < 2 {
   115  		go grep(&grepCommand{"<stdin>", os.Stdin}, re)
   116  	} else {
   117  		showname = len(a[1:]) > 1
   118  		// generate a chan of file names, bounded by the size of the chan. This in turn
   119  		// throttles the opens.
   120  		treenames := make(chan string, 128)
   121  		go func() {
   122  			for _, v := range a[1:] {
   123  				// we could parallelize the open part but people might want
   124  				// things to be in order. I don't care but who knows.
   125  				// just ignore the errors. If there is not a single one that works,
   126  				// then all the sizes will be 0 and we'll just fall through.
   127  				filepath.Walk(v, func(name string, fi os.FileInfo, err error) error {
   128  					if fi.IsDir() && !*recursive {
   129  						fmt.Fprintf(os.Stderr, "grep: %v: Is a directory\n", name)
   130  						return filepath.SkipDir
   131  					}
   132  					if err != nil {
   133  						fmt.Fprintf(os.Stderr, "%v: %v\n", name, err)
   134  						return err
   135  					}
   136  					treenames <- name
   137  					return nil
   138  				})
   139  			}
   140  			close(treenames)
   141  		}()
   142  
   143  		files := make(chan *grepCommand)
   144  		// convert the file names to a stream of os.File
   145  		go func() {
   146  			for i := range treenames {
   147  				fp, err := os.Open(i)
   148  				if err != nil {
   149  					fmt.Fprintf(os.Stderr, "can't open %s: %v\n", i, err)
   150  					continue
   151  				}
   152  				files <- &grepCommand{i, fp}
   153  			}
   154  			close(files)
   155  		}()
   156  		// now kick off the greps
   157  		// bug: file name order is not preserved here. Darn.
   158  
   159  		for f := range files {
   160  			go grep(f, re)
   161  		}
   162  	}
   163  
   164  	for c := range allGrep {
   165  		for r := range c.c {
   166  			// exit on first match.
   167  			if *quiet {
   168  				os.Exit(0)
   169  			}
   170  			printmatch(r)
   171  		}
   172  		nGrep--
   173  		if nGrep == 0 {
   174  			break
   175  		}
   176  	}
   177  	if *quiet {
   178  		os.Exit(1)
   179  	}
   180  }