Table of Contents
Back to Blog

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.

POSIX grepGNU grepBSD grep
Published: May 14, 2026Updated: May 14, 20269 min readAuthor: Line Counter Editorial Team
grepUnixLinuxmacOSTutorial

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 pattern
  • grep count total lines
  • grep count lines per file
  • grep -v count lines for inverse counts
  • grep -c vs wc -l when 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 lines
  • grep -c '.' counts lines containing at least one character, including spaces
  • grep -vc '^[[:space:]]*$' is better when you really want lines containing non-whitespace text
  • grep -Fc is 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 lines
  • wc -l counts 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 file behavior 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 f2 prints one count per file
  • wc -l f1 f2 prints one count per file plus a total
  • sed -n '$=' f1 f2 treats the inputs as one continuous stream by default
  • awk 'END{print NR}' f1 f2 gives 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 empty
  • grep -vc '^[[:space:]]*$' counts lines that contain some non-whitespace content
  • grep -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 C locale 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.txt took about 0.07s
  • grep -c '' big.txt ran below single-run timer resolution
  • grep -c '123' big.txt also ran below single-run timer resolution
  • awk 'END{print NR}' big.txt took about 0.18s
  • sed -n '$=' big.txt took about 0.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 -c for grep count lines matching pattern
  • use grep -vc for inverse counts
  • use grep -c '' when you want logical total lines and grep's per-file behavior
  • use wc -l when 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

I also verified the edge cases locally with GNU grep 3.11 on Linux:

  • grep -c '' returns 2 for a two-line file whose final line has no trailing newline
  • wc -l returns 1 for that same file
  • grep -c '' file1 file2 prints one filename:count result per file
  • grep -vc '^$' correctly counts lines that are not empty
  • grep -c '' on an empty file prints 0 and exits with status 1
  • grep -n restarts line numbering inside each file in a multi-file search

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