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