github.com/charlievieth/fastwalk@v1.0.3/examples/fwwc/main.go (about) 1 // fwwc is a an example program that recursively walks directories and 2 // prints the number of lines in each file it encounters. 3 package main 4 5 import ( 6 "bytes" 7 "flag" 8 "fmt" 9 "io" 10 "io/fs" 11 "os" 12 "path/filepath" 13 14 "github.com/charlievieth/fastwalk" 15 ) 16 17 var newLine = []byte{'\n'} 18 19 // countLinesInFile returns the number of newlines ('\n') in file name. 20 func countLinesInFile(name string) (int64, error) { 21 f, err := os.Open(name) 22 if err != nil { 23 return 0, err 24 } 25 defer f.Close() 26 27 buf := make([]byte, 16*1024) 28 var lines int64 29 for { 30 n, e := f.Read(buf) 31 if n > 0 { 32 lines += int64(bytes.Count(buf[:n], newLine)) 33 } 34 if e != nil { 35 if e != io.EOF { 36 err = e 37 } 38 break 39 } 40 } 41 return lines, err 42 } 43 44 func LineCount(root string, followLinks bool) error { 45 countLinesWalkFn := func(path string, d fs.DirEntry, err error) error { 46 // We wrap this with fastwalk.IgnorePermissionErrors so we know the 47 // error is not a permission error (common when walking outside a users 48 // home directory) and is likely something worse so we should return it 49 // and abort the walk. 50 // 51 // A common error here is "too many open files", which can occur if the 52 // walkFn opens, but does not close, files. 53 if err != nil { 54 return err 55 } 56 57 // If the entry is a symbolic link get the type of file that 58 // it references. 59 typ := d.Type() 60 if typ&fs.ModeSymlink != 0 { 61 if fi, err := fastwalk.StatDirEntry(path, d); err == nil { 62 typ = fi.Mode().Type() 63 } 64 } 65 66 // Skip dot (".") files (but allow "." / PWD as the path) 67 if path != "." && typ.IsDir() { 68 name := d.Name() 69 if name == "" || name[0] == '.' || name[0] == '_' { 70 return fastwalk.SkipDir 71 } 72 return nil 73 } 74 if typ.IsRegular() { 75 lines, err := countLinesInFile(path) 76 if err == nil { 77 fmt.Printf("%8d %s\n", lines, path) 78 } else { 79 // Print but do not return the error. 80 fmt.Fprintf(os.Stderr, "%s: %s\n", path, err) 81 } 82 } 83 return nil 84 } 85 86 // Ignore permission errors traversing directories. 87 // 88 // Note: this only ignores permission errors when traversing directories. 89 // Permission errors may still be encountered when accessing files. 90 walkFn := fastwalk.IgnorePermissionErrors(countLinesWalkFn) 91 92 conf := fastwalk.Config{ 93 // Safely follow symbolic links. This can also be achieved by 94 // wrapping walkFn with fastwalk.FollowSymlinks(). 95 Follow: followLinks, 96 97 // If NumWorkers is ≤ 0 the default is used, which is sufficient 98 // for most use cases. 99 } 100 // Note: Walk can also be called with a nil Config, in which case 101 // fastwalk.DefaultConfig is used. 102 if err := fastwalk.Walk(&conf, root, walkFn); err != nil { 103 return fmt.Errorf("walking directory %s: %w", root, err) 104 } 105 return nil 106 } 107 108 const UsageMsg = `Usage: %[1]s [-L] [PATH...]: 109 110 %[1]s prints the number of lines in each file it finds, 111 ignoring directories that start with '.' or '_'. 112 113 ` 114 115 func main() { 116 flag.Usage = func() { 117 fmt.Fprintf(os.Stdout, UsageMsg, filepath.Base(os.Args[0])) 118 flag.PrintDefaults() 119 } 120 followLinks := flag.Bool("L", false, "Follow symbolic links") 121 flag.Parse() 122 123 args := flag.Args() 124 if len(args) == 0 { 125 args = append(args, ".") 126 } 127 for _, root := range args { 128 // fmt.Println("ROOT:", root) 129 if err := LineCount(root, *followLinks); err != nil { 130 fmt.Fprintln(os.Stderr, "error:", err) 131 os.Exit(1) 132 } 133 } 134 }