Commandgrep

Search text in files recursively

Searches every file under a directory for a text pattern and prints the matching line number, so you can jump straight to it in an editor.

Last updated

Commandgrep
grep -rn "TODO" .

How it works

-r makes grep recurse into every subdirectory starting from the given path instead of only checking files you name explicitly; -n prefixes each match with its line number, which is what turns the output into something you can act on immediately (jump to that exact line) rather than just confirming a file contains the term somewhere. The search pattern comes first, the starting path last — quoting the pattern protects it from the shell if it contains spaces or characters that look like glob wildcards.

This is the everyday "where did I leave a TODO/FIXME/console.log" check across an entire project, needing nothing beyond grep itself.

Watch out for

  • →grep's pattern is a regex by default (basic regular expressions unless you pass -E), so characters like ., *, [ have special meaning — escape them for a literal search, or add -F to force a fixed-string match with no regex interpretation at all.
  • →--include="*.js" scopes a recursive search to matching files and --exclude-dir=node_modules skips noisy directories; both work on GNU grep and on macOS's BSD grep. The reasons to reach for ripgrep or GNU grep on macOS are speed and PCRE support, not these flags.
  • →Recursing through a tree that includes .git or node_modules is slow and produces a lot of irrelevant noise — pair -r with --exclude-dir=.git --exclude-dir=node_modules (GNU grep) to skip them.

Part by part

-rn
Two flags in one argument. grep accepts them separately too, but this is the form that appears in practice.
r
Recurse into subdirectories. Without it grep expects files as arguments and complains that the directory is a directory.
n
Print line numbers. Cheap to add, and the difference between a result you can jump to and one you have to hunt for.
"TODO"
The pattern, quoted so the shell does not interpret it. Essential the moment the pattern contains a space, an asterisk or a dollar sign.
.
Where to search. Required with -r on most implementations, even though it looks like it should default to here.

Common mistakes

  • grep -rn TODO .

    Works for a single bare word and breaks the moment the pattern has a space or a shell metacharacter. Quoting always is the habit worth having rather than remembering when it matters.

  • grep -rn "TODO" . in a repository with node_modules

    grep has no concept of ignore files, so it searches every dependency and build artefact. The result is thousands of irrelevant matches and a long wait — use --exclude-dir, or ripgrep, which respects .gitignore.

  • grep -rn "a.b" .

    The dot is a regex metacharacter matching any character, so this also finds axb and a1b. Use -F for a literal search, or escape the dot, when the pattern is meant to be exact text.

Questions

How do I skip node_modules and .git?

--exclude-dir can be repeated: --exclude-dir=node_modules --exclude-dir=.git. Both GNU and BSD grep support it, despite occasional claims otherwise.

How do I search only certain file types?

--include takes a glob, as in --include="*.ts". Quote it so the shell passes the pattern through, and combine several by repeating the flag.

Should I use ripgrep instead?

For searching a codebase, usually yes. It respects .gitignore by default, is substantially faster, and needs no exclusion flags — which removes the most common cause of unusable grep output.

How do I see context around each match?

-A prints lines after, -B before, and -C both. grep -rn -C 2 "TODO" . gives two lines either side, which is usually enough to tell whether a match is the one you wanted.

Related in Commands