Table of Contents
grep Guide
How to Count Lines with grep (-c Flag, Total Lines, and the Multi-File Advantage)
Count lines with grep using -c for matches, grep -c '' for total lines, and grep -vc for non-matching lines. Covers multi-file per-file counts, GNU grep vs BSD grep compatibility, missing trailing newline behavior, and grep vs wc -l tradeoffs.
If you need a fast grep count lines command, start here:
grep -c 'ERROR' app.log
That is the standard grep -c count lines pattern. It counts matching lines without printing the lines themselves.
But there is a second grep trick that is far less widely known:
grep -c '' file.txt
That counts total lines, because the empty pattern matches every line.
And there is a third detail that makes grep especially useful in shell workflows: grep count lines per file already behaves the way many people wish sed and awk behaved by default.
grep -c '' file1.txt file2.txt
prints one count per file instead of treating the inputs as one long stream.
This guide separates the real grep counting jobs:
grep count lines matching patterngrep count total linesgrep count lines per filegrep -v count linesfor inverse countsgrep -c vs wc -lwhen the file does not end with a newline
One correction matters up front: grep -c '' is not the same as wc -l on every file. GNU grep silently supplies a missing final newline, so grep count total lines can be one higher than wc -l when the last line is unterminated.
30-Second Cheat Sheet
- Count matching lines:
grep -c 'ERROR' app.log - Count total logical lines:
grep -c '' file.txt - Count total newline characters:
wc -l < file.txt - Count each file separately:
grep -c '' file1.txt file2.txt - Count non-matching lines:
grep -vc 'ERROR' app.log - Count case-insensitively:
grep -ic 'error' app.log - Count recursively:
grep -rc 'ERROR' ./logs/ - Keep strict shell scripts alive on zero matches:
count=$(grep -c 'ERROR' app.log || true)
If all you need is grep count lines matching pattern, stop at the first bullet. The rest of this article is about the edge cases that make grep count lines in file more interesting than it looks.
Method 1: grep -c for Matching Lines
This is the core grep count lines pattern:
grep -c 'ERROR' app.log
-c means "print only a count of selected lines per file." It is part of standard grep, so this grep -c count lines form is portable.
Common variations:
grep -c 'ERROR' app.log
grep -c '^$' file.txt
grep -c '.' file.txt
grep -ic 'error' app.log
grep -Ec 'ERROR|WARN|FATAL' app.log
grep -Fc 'exact string' file.txt
A few practical notes:
grep -c '^$'counts truly empty linesgrep -c '.'counts lines containing at least one character, including spacesgrep -vc '^[[:space:]]*$'is better when you really want lines containing non-whitespace textgrep -Fcis often faster than regex mode when you are searching for a literal string
If you searched for grep count lines matching pattern, this is the section you wanted.
The exit-code trap
This catches a lot of shell scripts:
grep -c 'NOTFOUND' file.txt
The command prints:
0
but grep still exits with status 1, because no selected lines were found.
That matters under set -e. A count of zero is valid data, but grep treats it as "no match."
Safe shell forms:
count=$(grep -c 'ERROR' app.log || true)
count=$(grep -c 'ERROR' app.log 2>/dev/null || echo 0)
The same trap appears in grep count total lines on an empty file:
grep -c '' empty.txt
It prints 0 and still exits with status 1.
Method 2: grep -c '' for Total Lines
This is the underrated grep count total lines trick:
grep -c '' file.txt
The empty BRE matches every line, so grep -c '' becomes a total-line counter.
That makes grep count lines in file possible even when you are not filtering for any real pattern.
Why it is not the same as wc -l
This is the crucial edge case:
printf 'a\nb' > no-final-newline.txt
grep -c '' no-final-newline.txt
wc -l < no-final-newline.txt
On this machine, grep returned 2 and wc -l returned 1.
Why? GNU grep documents that if the final byte of an input file is not a newline, grep silently supplies one. So grep -c '' counts logical lines, while wc -l counts newline characters.
That means grep -c vs wc -l is not just a speed question. It is also a definition question:
grep -c ''counts selected lineswc -lcounts line-feed bytes
If the file does end with a trailing newline, the two commands usually agree.
Empty files
On an empty file:
grep -c '' empty.txt
grep prints:
0
but exits with status 1 because zero lines were selected.
So grep count total lines is script-safe only if you remember the exit code rule.
When to use it
Use grep -c '' when:
- you are already in a grep-based workflow
- you want
grep count lines per filebehavior across many inputs - you care about the logical last line even when it has no terminating newline
Use wc -l when:
- you want the classic Unix newline count
- you only need a plain total and nothing else
- you want the most obvious command for other shell users reading the script
Method 3: Multi-File Counting Is grep's Real Advantage
This is where grep becomes especially attractive.
With multiple files:
grep -c '' file1.txt file2.txt
grep prints one result per file:
file1.txt:3
file2.txt:4
That is the key grep count lines per file behavior.
It is also one of the cleanest cross-tool contrasts in this whole series:
grep -c '' f1 f2prints one count per filewc -l f1 f2prints one count per file plus a totalsed -n '$=' f1 f2treats the inputs as one continuous stream by defaultawk 'END{print NR}' f1 f2gives one cumulative total unless you write per-file logic
So when people search grep count lines per file, grep is already doing the right thing without extra scripting.
Summing many grep counts safely
If you want one total across many files, suppress file names first:
grep -ch '' *.txt | awk '{sum += $1} END {print sum}'
That is safer than splitting filename:count on :, because file names can contain colons.
The same pattern works for matched lines:
grep -ch 'ERROR' *.log | awk '{sum += $1} END {print sum}'
Recursive counts
This is the recursive version:
grep -rc 'ERROR' ./logs/
That produces one filename:count result per file under the directory tree.
If you want the grand total:
grep -rch 'ERROR' ./logs/ | awk '{sum += $1} END {print sum}'
Per-file line numbers behave the same way
This per-file model is consistent with grep's line numbering too:
grep -n 'ERROR' file1.txt file2.txt
The line numbers restart inside each file, which matches the overall grep count lines per file design.
Method 4: grep -vc for Non-Matching Lines
This is the shortest grep -v count lines pattern:
grep -vc 'ERROR' app.log
-v inverts the selection. -c counts the selected lines. So grep -v count lines means "count lines that do not match."
Useful examples:
grep -vc 'ERROR' app.log
grep -vc '^$' file.txt
grep -vc '^[[:space:]]*#' config.ini
grep -vc '^[[:space:]]*$' file.txt
Read those carefully:
grep -vc '^$'counts lines that are not emptygrep -vc '^[[:space:]]*$'counts lines that contain some non-whitespace contentgrep -vc '^[[:space:]]*#'counts lines that are not comment lines beginning with#
This is one of grep's best counting tricks because there is no equally concise wc -l equivalent.
The identity check
For any file and pattern:
match=$(grep -c 'ERROR' app.log || true)
non_match=$(grep -vc 'ERROR' app.log || true)
total=$(grep -c '' app.log || true)
printf '%s + %s = %s\n' "$match" "$non_match" "$total"
You should get:
match + non_match = total
That makes grep -v count lines a nice way to sanity-check more complex filters.
Method 5: GNU grep vs BSD grep, and Why grep Is Fast
For the counting options in this guide, gnu grep vs bsd grep is not where the real trouble is.
These forms are portable:
grep -c 'pattern' file.txt
grep -vc 'pattern' file.txt
grep -ic 'pattern' file.txt
grep -Ec 'foo|bar' file.txt
grep -Fc 'literal text' file.txt
So grep count lines, grep count lines in file, and grep count lines per file do not need special GNU-only syntax.
The GNU-specific parts in this article are mainly about documentation detail and performance discussion, not the core counting flags.
Why grep can be surprisingly fast
GNU grep's performance notes make two practical points that show up in real shell work:
- fixed-string searches can be faster than regex searches
- the
Clocale can be faster than a locale with heavier collation rules
That is why these variants are worth keeping:
grep -Fc 'exact string' file.txt
LC_ALL=C grep -c 'ERROR' large.log
GNU grep's performance chapter also explains that some regex features, especially back-references and large repetition counts, can make searches much slower.
Local benchmark notes
On this Linux machine with GNU grep 3.11, mawk 1.3.4, GNU sed 4.9, and a hot-cache synthetic file of about 105 MB with 5,000,000 lines:
wc -l < big.txttook about0.07sgrep -c '' big.txtran below single-run timer resolutiongrep -c '123' big.txtalso ran below single-run timer resolutionawk 'END{print NR}' big.txttook about0.18ssed -n '$=' big.txttook about0.30s
Those grep rows should be read as "extremely fast on this hot-cache workload," not as universal numbers. In other environments, storage, locale, pattern complexity, and cache state can easily change the ranking.
So the practical grep -c vs wc -l conclusion is this:
- use
grep -cforgrep count lines matching pattern - use
grep -vcfor inverse counts - use
grep -c ''when you want logical total lines and grep's per-file behavior - use
wc -lwhen you want the classic newline counter and the most obvious total-line command
Method 6: Practical grep Counting Snippets
Here are the grep patterns worth keeping around.
Count log levels
for level in DEBUG INFO WARN ERROR FATAL; do
count=$(grep -ic "$level" app.log || true)
printf '%-8s %s\n' "$level" "$count"
done
Sort files by total line count
grep -rc '' ./src/ | sort -t: -k2,2nr | head -20
Count Python lines that are not blank and not comments
grep -Evc '^[[:space:]]*$|^[[:space:]]*#' *.py
Watch a log file grow
watch -n 1 'grep -c "" /var/log/app.log'
Sum a recursive match count
grep -rch 'ERROR' ./logs/ | awk '{sum += $1} END {print sum}'
Count fixed strings faster than regex mode
grep -Fic 'timeout' app.log
That is still plain grep count lines, just tuned for literal strings instead of regex syntax.
Sources Checked
- GNU grep manual, "Introduction": https://www.gnu.org/software/grep/manual/html_node/Introduction.html
- GNU grep manual, "Command-line Options": https://www.gnu.org/software/grep/manual/html_node/Command_002dline-Options.html
- GNU grep manual, "Performance": https://www.gnu.org/software/grep/manual/html_node/Performance.html
- POSIX
grepspecification: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html - FreeBSD
grep(1)manual page: https://man.freebsd.org/cgi/man.cgi?query=grep&sektion=1
I also verified the edge cases locally with GNU grep 3.11 on Linux:
grep -c ''returns2for a two-line file whose final line has no trailing newlinewc -lreturns1for that same filegrep -c '' file1 file2prints onefilename:countresult per filegrep -vc '^$'correctly counts lines that are not emptygrep -c ''on an empty file prints0and exits with status1grep -nrestarts line numbering inside each file in a multi-file search
Related Guides and Tools
Need to count lines without grep pattern syntax?
Try the Line Counter. No -c flags, no exit-code surprises, just the number.
Frequently Asked Questions
How do I count lines with grep?
Use grep -c 'pattern' file.txt for matching lines, grep -c '' file.txt for total logical lines, and grep -vc 'pattern' file.txt for non-matching lines.
How do I count total lines with grep?
Use grep -c '' file.txt. The empty pattern matches every line, so grep counts all selected lines instead of only matching content.
How do I count lines per file with grep?
Pass multiple files directly to grep -c. grep already keeps them separate and prints one filename:count result per file.
What is the difference between grep -c '' and wc -l?
grep -c '' counts logical lines selected by grep, including a final unterminated line. wc -l counts newline characters, so it can be lower by one when the file does not end with a newline.
How do I count non-matching lines with grep?
Use grep -vc 'pattern' file.txt. The -v flag inverts the selection and -c counts the remaining lines.
Does grep -c work the same on GNU grep and BSD grep?
For the options in this guide, yes. The core counting flags -c, -v, -i, -E, and -F are portable across POSIX-style grep implementations.
Related Guides
16 min read
How to Count Lines in Bash: The Complete Guide with Edge Cases
Master line counting in Bash: count lines in files, variables, command output, and directories. Covers wc -l pitfalls, empty files, filenames with spaces, and shell script usage.
10 min read
How to Count Lines in AWK (NR, FNR, and Why They're Different With Multiple Files)
Count lines in AWK with END{print NR}, per-file FNR patterns, RS record separator changes, and match counting. Covers NR vs FNR, GNU awk ENDFILE, nonblank-line counting, and when wc -l is the better tool.
9 min read
How to Count Lines in sed (And Why $= Is Not the Same as $p)
Count lines in sed with sed -n '$=', understand the $= vs $p difference, handle multiple files, and avoid CRLF surprises. Covers GNU sed vs BSD sed, pattern-based counting, and when wc -l is the better tool.
20 min read
How to Count Lines in Python: 7 Methods, Benchmarked and Battle-Tested
Count lines in Python strings, text files, large files, and directories. Includes real performance benchmarks, empty file handling, splitlines vs split, and production-ready functions.