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 }