I used to open files to search for things.
A config had the wrong database host somewhere. I knew it was in one of the files in /etc/nginx/ but I didn't know which one. So I opened nginx.conf. Searched. Not there. Opened sites-available/default. Searched. Not there either. Opened three more files. Found it in the fourth one.
That took about 4 minutes. The grep command that does the same thing takes less than a second:
grep -rn "db.old-host.com" /etc/nginx/
Every file. Every subdirectory. Exact filename. Exact line number. Done.
I genuinely don't understand why it took me so long to make this my default workflow instead of manually opening files like it's 2004.
The Script
#!/bin/bash
# Search files for text — grep reference
KEYWORD="TODO" # ← what to search for
SEARCH_DIR="$HOME" # ← where to search
FILE_TYPE="*.txt" # ← file types to include
echo "Searching for '$KEYWORD' in $SEARCH_DIR..."
echo "────────────────────────────────────────"
grep -rn "$KEYWORD" "$SEARCH_DIR" \
--include="$FILE_TYPE" \
--color=auto
COUNT=$(grep -rc "$KEYWORD" "$SEARCH_DIR" \
--include="$FILE_TYPE" 2>/dev/null \
| grep -v ":0$" | wc -l)
echo "────────────────────────────────────────"
echo "Found in $COUNT file(s)"
But honestly, you'll use the raw command more than the script. The script is a reference. The command is the weapon.
The Flags That Actually Matter
grep -r — recursive. Searches every file in the directory and all subdirectories. Without this, grep only searches the one file you hand it.
grep -n — line numbers. Instead of just showing the matching line, it shows filename:42: the matching line. Now you know exactly where to look.
grep -rn — both together. This is the one I type the most. grep -rn "ERROR" /var/log/ and I immediately see every error across every log file with the exact line number.
*`--include=".py"** — filter by file type. If you only want to search Python files, or only .conf files, or only .html` files, this prevents grep from wasting time on binary files and irrelevant formats.
--color=auto — highlights the matched keyword in the output. When you're scanning 50 results, the color makes the keyword jump out instantly.
-i — case-insensitive. grep -rni "error" /var/log/ catches "Error", "ERROR", "error", and any other capitalization.
-l — filenames only. When you don't need to see the matching lines, just which files contain the keyword. grep -rl "TODO" ./src/ gives you a clean list of files.
-c — count matches per file. grep -rc "TODO" ./src/ shows filename:3 meaning 3 matches in that file.
The Combinations I Use Every Week
Find every TODO in a project:
grep -rn "TODO" ./src/ --include="*.js"
Find which config file has a specific setting:
grep -rn "max_connections" /etc/
Find a function definition across a codebase:
grep -rn "def process_order" ./app/ --include="*.py"
Find IP addresses in log files:
grep -rn "192.168" /var/log/ --include="*.log"
Find files that contain a string but don't care about the matching lines:
grep -rl "database_url" ./config/
grep vs. Your Editor's Search
Your editor's search (Ctrl+Shift+F in VS Code, Cmd+Shift+F on Mac) does basically the same thing — but only for files that are open in your project.
grep works on any directory on any machine, including servers where you only have SSH access and no editor at all. When you're debugging a production issue over SSH at 11 PM, grep -rn is the fastest path from "I know the error is in here somewhere" to the exact line.
It's also faster on large directories because it doesn't need to index anything first. It just reads files sequentially and matches the pattern.
One Gotcha Worth Knowing
grep on binary files produces garbage output. If you're searching a directory that contains images, compiled files, or databases, add --binary-files=without-match to skip them:
grep -rn "search_term" /some/dir/ --binary-files=without-match
Or use --include to limit to specific text file types, which solves the same problem more precisely.
Full script, flag cheat sheet, and more combinations:
bashsnippets.xyz/snippets/search-files-for-text-grep.html
If you want to quickly look up the flags without reading a man page, I keep a reference table on the snippet page with every flag I've actually used in production.