File Hunting
Overview
File hunting is the art of finding content across a codebase. It combines:
-
find - locate files by name/type/time
-
grep - search content within files
-
awk - format and process output
This is the workflow you’ll use 10x daily as a senior engineer.
The Trifecta Pattern
Pattern 1: grep -rn (Quick Content Search)
Fastest for simple searches:
# Search for term, show file:line:content
grep -rn "borg_backups" ~/atelier/_bibliotheca/domus-* --include="*.adoc"
# Breakdown:
# -r = recursive
# -n = show line numbers
# --include = filter by extension
Pattern 2: find + grep -l (List Files Only)
When you just need filenames:
# Find files containing term
find ~/atelier/_bibliotheca -name "*.adoc" -exec grep -l "borg_backups" {} \;
# Breakdown:
# -exec grep -l {} \; = run grep on each file, print filename if match
# -l = list filenames only (not content)
Pattern 3: find + grep + awk (Formatted Output)
The power pattern - formatted columns:
# Find, grep with context, format with awk
find ~/atelier/_bibliotheca -name "*.adoc" -exec grep -Hn "borg_backups" {} \; | \
awk -F: '{printf "%-60s L%-4s %s\n", $1, $2, $3}'
# Breakdown:
# grep -Hn = -H (show filename), -n (show line number)
# awk -F: = split on colon (grep output: file:line:content)
# printf "%-60s" = left-align, 60 char width for filename
# L%-4s = "L" prefix + 4 char width for line number
Output:
/home/user/atelier/.../borg-backup.adoc L48 sudo mount -t nfs nas-01...
/home/user/atelier/.../backup-strategy.adoc L183 sudo mount -t nfs nas-01...
Real-World Hunting Scenarios
Find Where a Command Is Documented
# Where is the NFS mount command documented?
grep -rn "mount -t nfs" ~/atelier/_bibliotheca/domus-* --include="*.adoc" | head -20
# Just unique files
grep -rl "mount -t nfs" ~/atelier/_bibliotheca/domus-* --include="*.adoc" | sort -u
Find All References to an IP Address
# Find hardcoded IPs (should be attributes!)
grep -rn "10\.50\.1\.70" ~/atelier/_bibliotheca/domus-* --include="*.adoc"
# Count occurrences per file
grep -rc "10\.50\.1\.70" ~/atelier/_bibliotheca/domus-* --include="*.adoc" | \
awk -F: '$2 > 0 {print}'
Find Cross-Component xrefs (Potential Issues)
# Find xrefs with double-colon (cross-component)
grep -rn 'xref:[a-z-]*::' ~/atelier/_bibliotheca/domus-* --include="*.adoc" | \
awk -F: '{printf "%-50s L%-4s %s\n", $1, $2, $3}'
awk Formatting Cheatsheet
Field Splitting
# grep output is: file:line:content
# -F: splits on colon
awk -F: '{print $1}' # filename
awk -F: '{print $2}' # line number
awk -F: '{print $3}' # content (first part only!)
# Problem: content may have colons!
# Solution: print everything from field 3 onwards
awk -F: '{$1=$2=""; print $0}'
Building a Hunt Alias
Add to ~/.zshrc:
# Hunt for content across domus repos
hunt() {
local term="$1"
local ext="${2:-adoc}" # default to .adoc
find ~/atelier/_bibliotheca/domus-* -name "*.$ext" -exec grep -Hn "$term" {} \; | \
awk -F: '{printf "%-60s L%-4s %s\n", $1, $2, substr($0, index($0,$3))}'
}
# Usage:
# hunt "borg_backups"
# hunt "10.50.1.70"
# hunt "mount -t nfs" sh
Performance: grep -r vs find + grep
| Method | Pros | Cons |
|---|---|---|
|
Fastest for simple searches, built-in extension filter |
No time/size filters, less control |
|
Full find power (time, size, type), combine with any filter |
Slower (forks grep per file), more verbose |
|
Faster than |
More complex syntax |
|
Fastest, respects .gitignore, modern |
External tool, not always installed |
Common Mistakes
Mistake 1: Forgetting Quotes
# WRONG - shell expands *.adoc before find runs
find . -name *.adoc
# RIGHT - quotes prevent expansion
find . -name "*.adoc"
Find and Open (Interactive Hunting)
Open First Match in nvim
# Command substitution (opens all matches)
nvim $(find ~/atelier/_bibliotheca -name "file-hunting.adoc")
# First match only (-print -quit stops after finding one)
nvim $(find ~/atelier/_bibliotheca -name "backup-strategy*" -print -quit)
# xargs (handles spaces in paths safely)
find ~/atelier/_bibliotheca -name "*.adoc" -print0 | xargs -0 nvim
# -exec directly
find ~/atelier/_bibliotheca -name "nav.adoc" -exec nvim {} \;
Conditional Open (Only If Found)
# Store result, check if empty, then open
file=$(find ~/atelier/_bibliotheca -name "backup-strategy*" -print -quit) && \
[[ -n "$file" ]] && nvim "$file" || echo "Not found"
Shell Function for Daily Use
Add to ~/.zshrc:
# Find and edit - opens first match in nvim
fe() {
local file
file=$(find ~/atelier/_bibliotheca -name "$1" -print -quit)
[[ -n "$file" ]] && nvim "$file" || echo "Not found: $1"
}
# Find and edit with content search - opens file containing term
fec() {
local file
file=$(grep -rl "$1" ~/atelier/_bibliotheca/domus-* --include="*.adoc" | head -1)
[[ -n "$file" ]] && nvim "$file" || echo "Not found: $1"
}
Usage:
fe "backup-strategy*" # Find by filename
fe "nav.adoc" # Exact name
fec "borg_backups" # Find by content
fec "mount -t nfs" # Find command documentation
fzf Integration (Fuzzy Finding)
If you have fzf installed:
# Interactive file picker
nvim $(find ~/atelier/_bibliotheca -name "*.adoc" | fzf)
# With preview
find ~/atelier/_bibliotheca -name "*.adoc" | \
fzf --preview 'head -50 {}' | xargs nvim
# Shell function
fef() {
local file
file=$(find ~/atelier/_bibliotheca -name "*.adoc" | fzf --preview 'head -40 {}')
[[ -n "$file" ]] && nvim "$file"
}
Quick Reference
# Quick content search
grep -rn "term" path --include="*.ext"
# Files containing term
find path -name "*.ext" -exec grep -l "term" {} \;
# Formatted output
find path -name "*.ext" -exec grep -Hn "term" {} \; | \
awk -F: '{printf "%-60s L%-4s %s\n", $1, $2, $3}'
# Count per file
grep -rc "term" path --include="*.ext" | awk -F: '$2>0'
# Modified today + content
find path -name "*.ext" -mtime 0 -exec grep -l "term" {} \;
# Unique files only
grep -rl "term" path --include="*.ext" | sort -u