From c5f0f0231c0e48e1e9cd338a6d98dfe6a912235e Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Fri, 15 Aug 2025 22:26:18 -0500 Subject: [PATCH 1/5] add line and col to output for richer results --- main.go | 25 +++++++++++--- pkg/scanner.go | 91 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 96 insertions(+), 20 deletions(-) diff --git a/main.go b/main.go index 569a21a..6f71219 100644 --- a/main.go +++ b/main.go @@ -79,6 +79,16 @@ func main() { truthy values are: 1, t, T, true, True, TRUE falsy values are: 0, f, F, false, False, FALSE`) + var showLines bool + flag.BoolVar(&showLines, "l", false, `OPTIONAL Scnnr MODE + show line numbers for each match (requires -k) + when enabled, finds ALL matches in files (no early exit)`) + + var showCols bool + flag.BoolVar(&showCols, "c", false, `OPTIONAL Scnnr MODE + show line and column numbers for each match (requires -k) + when enabled, finds ALL matches in files (no early exit)`) + var paths string flag.StringVar(&paths, "p", "", `REQUIRED NameFinder MODE any absolute path - can be comma delimited: Example: $HOME or '/tmp,/usr'`) @@ -95,7 +105,8 @@ func main() { directory = dir - if mode == "fnf" { + switch mode { + case "fnf": scanFuzzy := strings.Split(fuzzy, ",") scanPaths := strings.Split(paths, ",") @@ -108,15 +119,21 @@ func main() { for _, file := range nfnf.Files { fmt.Println(file) } - } else if mode == "scn" { + case "scn": extensions = strings.Split(ext, ",") keywords = strings.Split(kwd, ",") + if (showLines || showCols) && keywords[0] == "" { + log.Fatal("Position tracking flags (-l, -c) require keywords (-k)") + } + scanner := scnnr.Scanner{ Regex: rgx, Keywords: keywords, Directory: directory, FileExtensions: extensions, + ShowLines: showLines, + ShowCols: showCols, } err := scanner.Scan() @@ -124,7 +141,7 @@ func main() { if err != nil { log.Fatal(err) } - } else if mode == "fsf" { + case "fsf": nfsf := scnnr.NewFileSizeFinder(size) nfsf.Scan(directory) @@ -132,7 +149,7 @@ func main() { for _, file := range nfsf.Files { fmt.Println(file) } - } else if mode == "fff" { + case "fff": keywords = strings.Split(kwd, ",") if keywords[0] == "" { diff --git a/pkg/scanner.go b/pkg/scanner.go index 6064817..7e20199 100644 --- a/pkg/scanner.go +++ b/pkg/scanner.go @@ -15,10 +15,13 @@ import ( type Scanner struct { sync.Mutex Regex bool + ShowLines bool + ShowCols bool Directory string FileExtensions []string Keywords []string KeywordMatches []string + AllMatches []Match MatchedFilePaths []FileData } @@ -28,6 +31,14 @@ type FileData struct { Info os.FileInfo } +// Match represents a single keyword match with position information +type Match struct { + Path string + Line int + Column int + Keyword string +} + // Scan walks the given directory tree and stores all matching files into a slice func (s *Scanner) Scan() error { err := filepath.Walk(s.Directory, s.scan) @@ -61,13 +72,41 @@ func (s *Scanner) Scan() error { wg.Wait() } - } - fmt.Println(strings.Join(s.KeywordMatches, "\n")) + // Output results based on position tracking settings + if s.ShowLines || s.ShowCols { + s.outputPositionMatches() + } else { + // Original behavior + fmt.Println(strings.Join(s.KeywordMatches, "\n")) + } + } return nil } +// outputPositionMatches formats and outputs matches with position information +func (s *Scanner) outputPositionMatches() { + for _, match := range s.AllMatches { + output := match.Path + + if s.ShowCols { + // -c flag shows both line and column + output = fmt.Sprintf("%s:%d:%d", output, match.Line, match.Column) + } else if s.ShowLines { + // -l flag shows only line + output = fmt.Sprintf("%s:%d", output, match.Line) + } + + // If multiple keywords, append the matching keyword + if len(s.Keywords) > 1 { + output = fmt.Sprintf("%s:%s", output, match.Keyword) + } + + fmt.Println(output) + } +} + func (s *Scanner) scan(path string, info os.FileInfo, err error) error { if err != nil { return err @@ -106,38 +145,58 @@ func (s *Scanner) parse(match FileData) { found := false + lineNumber := 0 + + positionTracking := s.ShowLines || s.ShowCols + for scanner.Scan() { line := scanner.Text() + lineNumber++ + for i := 0; i < len(s.Keywords); i++ { - if found { + if !positionTracking && found { break } + matchFound := false + var column int + if s.Regex { re := regexp.MustCompile(s.Keywords[i]) - if re.Match([]byte(line)) { - s.Lock() - - s.KeywordMatches = append(s.KeywordMatches, match.Path) - - found = true - - s.Unlock() - - break + if loc := re.FindStringIndex(line); loc != nil { + matchFound = true + column = loc[0] + 1 } } else { - if strings.Contains(line, s.Keywords[i]) { - s.Lock() + if idx := strings.Index(line, s.Keywords[i]); idx != -1 { + matchFound = true + column = idx + 1 + } + } + if matchFound { + s.Lock() + + if positionTracking { + match := Match{ + Path: match.Path, + Line: lineNumber, + Column: column, + Keyword: s.Keywords[i], + } + + s.AllMatches = append(s.AllMatches, match) + } else { s.KeywordMatches = append(s.KeywordMatches, match.Path) found = true + } - s.Unlock() + s.Unlock() + if !positionTracking { break } } From 99d0d09c9b2cc1b76596b074c86847d4d3712ca6 Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Fri, 15 Aug 2025 22:34:01 -0500 Subject: [PATCH 2/5] update e2e --- scripts/e2e.sh | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/scripts/e2e.sh b/scripts/e2e.sh index 1ac9350..2713f3e 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -39,3 +39,19 @@ echo "--- FILE FINGERPRINT FINDER DRY RUN: BEGIN---" go run main.go -m fff -k $(go run cmd/checksum/main.go) -d . echo "--- FILE FINGERPRINT FINDER DRY RUN: DONE---" + +sleep 2 + +echo "--- SCANNER LINE RUN: BEGIN---" + +go run main.go -k main,const,let,var,for -p $HOME -c + +echo "--- SCANNER LINE RUN: DONE ---" + +sleep 2 + +echo "--- SCANNER LINE AND COL RUN: BEGIN---" + +go run main.go -k main,const,let,var,for -p $HOME -c + +echo "--- SCANNER LINE AND COL RUN: DONE ---" From 41d6d39693ecaa71cfb69e7dec098fa99f700711 Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Fri, 15 Aug 2025 22:34:28 -0500 Subject: [PATCH 3/5] fix e2e calls --- scripts/e2e.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/e2e.sh b/scripts/e2e.sh index 2713f3e..f131c17 100755 --- a/scripts/e2e.sh +++ b/scripts/e2e.sh @@ -44,7 +44,7 @@ sleep 2 echo "--- SCANNER LINE RUN: BEGIN---" -go run main.go -k main,const,let,var,for -p $HOME -c +go run main.go -k main,const,let,var,for -p $HOME -l echo "--- SCANNER LINE RUN: DONE ---" From 5591232fe63c8d9ea6ce2845c1241459f139adf9 Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Fri, 15 Aug 2025 22:48:52 -0500 Subject: [PATCH 4/5] update README with new output and examples --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/README.md b/README.md index 8f98d27..ccee8db 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ Call scnnr with the `-h` flag: ``` $ scnnr -h + -c OPTIONAL Scnnr MODE + show line and column numbers for each match (requires -k) + when enabled, finds ALL matches in files (no early exit) -d string OPTIONAL Scnnr MODE and OPTIONAL FingerrintFinder MODE directory where scnnr will scan @@ -63,6 +66,9 @@ $ scnnr -h if no keywords are given - all file paths of given file extensions will be returned if keywords are given - only filepaths of matches will be returned FingerprintFinder: this is a comma delimited list of SHA2-256 hashes to find files by + -l OPTIONAL Scnnr MODE + show line numbers for each match (requires -k) + when enabled, finds ALL matches in files (no early exit) -m string OPTIONAL mode that scnnr will run in @@ -137,6 +143,48 @@ README.md cmd/scanner.go ``` +### Keywords with line numbers + +```bash +scnnr_bins/README.md:117:cache +scnnr_bins/README.md:122:cache +scnnr_bins/README.md:129:cache +scnnr_bins/README.md:135:fileData +scnnr_bins/README.md:135:cache +README.md:123:cache +README.md:128:cache +README.md:135:cache +README.md:141:fileData +README.md:141:cache +scnnr_bins/README.md:377:cache +scnnr_bins/README.md:387:cache +README.md:383:cache +README.md:393:cache +pkg/scanner.go:215:fileData +pkg/scanner.go:222:fileData +``` + +### Keywords with both line numbers and column numbers + +```bash +scnnr_bins/README.md:117:53:cache +scnnr_bins/README.md:122:29:cache +scnnr_bins/README.md:129:22:cache +scnnr_bins/README.md:135:28:fileData +scnnr_bins/README.md:135:37:cache +scnnr_bins/README.md:377:36:cache +scnnr_bins/README.md:387:40:cache +pkg/scanner.go:215:9:fileData +pkg/scanner.go:222:26:fileData +README.md:123:53:cache +README.md:128:29:cache +README.md:135:22:cache +README.md:141:28:fileData +README.md:141:37:cache +README.md:383:36:cache +README.md:393:40:cache +``` + # File Name Finder (NameFinder) (fnf) ``` From a1af7dc0876c4312915719a69eae010b7431450b Mon Sep 17 00:00:00 2001 From: Regis Boudinot Date: Fri, 15 Aug 2025 23:03:47 -0500 Subject: [PATCH 5/5] update new feature console output --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index ccee8db..579bf61 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,7 @@ cmd/scanner.go ### Keywords with line numbers ```bash +$ scnnr -d . -e .md,.go -k fileData,cache -l scnnr_bins/README.md:117:cache scnnr_bins/README.md:122:cache scnnr_bins/README.md:129:cache @@ -167,6 +168,7 @@ pkg/scanner.go:222:fileData ### Keywords with both line numbers and column numbers ```bash +$ scnnr -d . -e .md,.go -k fileData,cache -c scnnr_bins/README.md:117:53:cache scnnr_bins/README.md:122:29:cache scnnr_bins/README.md:129:22:cache