Overview

Use find to select files, grep to select lines, awk for fields, and sed for line edits. Examples run on GNU tools; the BSD (macOS) differences are in the gotchas. Pure-bash alternatives are in bash-one-liners.

find

Put tests before actions: -delete placed before -name deletes everything.

ExpressionMatches or does
find . -type f -name '*.md'Markdown files, recursive.
find . -type f \( -name '*.ts' -o -name '*.tsx' \)Either pattern.
find . -not -path '*/node_modules/*'Exclude a subtree.
find . -type f -size +10MLarger than 10 MB.
find . -type f -mtime -7Modified in the last 7 days.
find . -mindepth 2 -maxdepth 3 -type fDepth-bounded.
find . -type f -name '*.tmp' -deleteDelete matches.
find . -name '*.log' -exec grep -l ERROR {} +Batch many files into one command.
find . -type f -print0 | xargs -0 wc -lSafe for names with spaces.

grep

Use rg for repository searches and grep -E when you need portability.

ExpressionDoes
grep -rn "pat" .Recursive with line numbers; skips symlinks found while walking.
grep -rIn "pat" --include='*.py' --exclude-dir=node_modules .Text files only, filtered by name.
grep -l / -L / -cMatching files / non-matching files / count per file.
grep -v / -i / -wInvert / ignore case / whole word.
grep -A 3 -B 2 "pat"Context after / before.
grep -E "(foo|bar)"Extended regex; no \d, use [0-9].
grep -P "\d{3}-\d{4}"PCRE; GNU grep only.
grep -o "pat"Print only the matched text.

Prefer rg (ripgrep) for repository searches; it honors .gitignore and skips binaries.

awk

Fields are $1, $2; $NF is the last; NR is the line number.

ExpressionDoes
awk '{print $1, $NF}' fFirst and last field.
awk -F: '{print $1}' /etc/passwdCustom delimiter.
awk 'NR>=3 && NR<=7' fLines 3 to 7.
awk '/pat/' / awk '!/pat/'Lines that match / do not match.
awk '{s+=$2} END{print s}' fSum column 2.
awk '!seen[$0]++' fRemove duplicate lines, keeping order.
awk '{gsub(/old/,"new")} 1' fReplace all matches, print every line.
awk 'NR==5' f / awk 'length($0) > 80' fLine 5 / lines longer than 80 characters.

sed

Use -i.bak so in-place edits work on both GNU and BSD.

ExpressionDoes
sed 's/old/new/g' fReplace all matches per line; without g, only the first.
sed -E 's/(a+)b/\1/' fExtended regex with groups.
sed -i.bak 's/old/new/g' fIn-place with backup; works on GNU and BSD.
sed '/pat/d' / sed '1d' / sed '$d'Delete matching / first / last line.
sed -n '10,20p'Print lines 10 to 20.
sed -n '/start/,/end/p'Print a pattern range.
sed 's/^/> /' / sed 's/$/;/'Prefix / suffix every line.
sed '/pat/a\new line'Append a line after each match (GNU one-line form).

Pipelines

Run a pipeline without -i or -delete first, then add the destructive step.

# Top 10 client IPs in an access log
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
 
# Rename a symbol across Python files (BSD sed: use -i '')
find . -name '*.py' -exec sed -i 's/old_fn/new_fn/g' {} +
 
# Files containing a TODO, deduplicated
grep -rn "TODO" . | awk -F: '{print $1}' | sort -u

Gotchas

Avoid these; each differs between GNU and BSD or breaks on unusual filenames.

GotchaFix
Unquoted -name *.md is expanded by the shell first.Quote it: -name '*.md'.
-exec cmd {} \; runs once per file.End with + to batch.
sed -i is GNU; BSD needs an argument (-i '').Use -i.bak for portability, then delete the .bak files.
grep -r skips symlinks and enters node_modules.Use -R to follow links; add --exclude-dir.
-P is GNU only (macOS grep lacks it), and \d is not POSIX: GNU grep -E matches a literal d.Use [0-9] with -E, or install GNU grep for -P.
awk '{print $1}' splits on spaces inside quoted CSV fields.Use a CSV-aware tool such as mlr (Miller) or csvcut (csvkit).
sed 's/./X/g' replaces every character.Escape the dot: \..