My Deep Dive into the grep Command in Linux

For a long time, I kept seeing the grep command mentioned in tutorials and Stack Overflow threads, but it always felt abstract. I knew it searched inside files, yet I did not fully understand why developers used it so often. That changed when I needed it in my own project.
While working on my AESL inventory tracking system (React frontend and FastAPI backend), I had to trace where the word api appeared across the codebase. I was debugging route imports and configuration paths, and I wanted to find every occurrence of that keyword. That is when grep became useful.
My first encounter with grep
The first command I tried was:
grep -l "api" .
The terminal replied:
grep: .: Is a directory
That confused me. I expected grep to scan everything under the current folder and return files that contained api. The message taught me this: grep searches files, not directories, unless you tell it to search recursively. The . means the current directory, but I had not asked grep to descend into it.
So I retried with recursion:
grep -r "api" .
That produced lines from different files, for example:
./frontend/src/pages/SimpleDashboard.jsx:const apiUrl = ...
./frontend/src/services/api.js:export const getData = ...
./main.py:app.include_router(api_router)
At that point I saw grep as a practical way to inspect the whole project quickly.
Useful flags I relied on
After the initial success I explored more flags to refine searches.
-i — ignore case
grep -ir "api" .
Matches api, API, or Api. Useful when I am not sure about capitalization.
-l — show filenames only
grep -irl "api" .
Prints only the names of files that contain at least one match. This is handy when I just need to know where something exists.
-c — count matches
grep -irc "api" .
Prints path:count for each file. That gave me a sense of how widely the term appeared.
Example output:
./frontend/src/pages/SimpleDashboard.jsx:3
./frontend/src/services/api.js:5
./main.py:2
Note that -c counts matches per file, including zeroes, which is why I often filter out :0 later.
-v — invert match
grep -irv "api" .
Shows lines that do not contain api. This is useful to filter out noisy matches or to inspect everything except a pattern.
--include — target file types
If I only want certain file types, I use --include. A typical pattern:
grep -irl --include="*.py" "api" .
That limits the search to Python files and keeps the output focused.
One small but important detail: options should be placed before the pattern and path to avoid confusion. If you omit the leading hyphen on short flags or put arguments in the wrong order, the command can fail or behave unexpectedly.
Figuring out -l and -c
In my exploration I tried grep -ircl "api" . . I thought it would list filenames and show counts. That was incorrect as I noted only the file names were listed out without the count.
-iignore case-rrecursive-ccount matches per file-llist filenames only
When -l and -c are both present, -l takes precedence. In practice:
grep -ircl "api" .
prints only filenames that contain a match. It does not print counts. -l overrides -c because -l requests filenames only, and grep follows that output mode.
Why this happens
-csays: “Show me the count of matches in each file.”-lsays: “Only list the names of files that contain at least one match.”
If you want counts, do not include -l. For example:
grep -irc "api" .
prints path:count for all files. To show only files that actually have matches, filter out zeros:
grep -irc "api" . | grep -v ':0'
To get counts sorted by frequency, use:
grep -irc --exclude-dir={.git,node_modules} "api" . | grep -v ':0' | sort -t: -k2 -nr
This prints path:count sorted by count descending. If you only want filenames without counts, use:
grep -irl --exclude-dir={.git,node_modules} "api" .
Those alternatives let you get the output format you actually need.
A few practical details I used
Use
--exclude-dirto skip heavy folders like.gitandnode_modulesso searches are faster:grep -ir --exclude-dir={.git,node_modules} "api" .Pipe into
lessfor long outputs:grep -ir "api" . | lessPress
qto quit.Combine
--includewith recursion to target file types:grep -ir --include="*.jsx" "api" frontendIf you want color highlighting inside
less, force color and useless -R:grep -irE --color=always "api|component" . | less -R
What I took away
Learning grep was straightforward once I had a real problem to solve. The important bits I keep in mind are:
Always be explicit about recursion when you want to search directories.
Flags change output modes in ways that matter.
-land-cdo not combine to give both names and counts. Pick one or post-process the output.Use
--includeand--exclude-dirto narrow the search scope.Pipe results to
less,sort,awk, orgrepagain when you need different formatting.
Final note
That initial error message, grep: .: Is a directory, was helpful. It forced me to understand how grep expects arguments and how to control its behavior. Now I use grep as a practical inspection tool when I need to find references in a codebase.


