To count lines in a file on Linux, run wc -l filename. The command counts newline characters so a file whose last line has no trailing newline reports one fewer than the lines you can actually read.
This guide covers wc, grep, awk, sed and Bash for the same job. It also handles blank lines, compressed files, whole directories and the edge cases that make two tools disagree. I ran every command here on GNU coreutils 9.4.
Linux count lines: the short answer
Five commands cover most cases. The first two use wc and run fastest. The awk and grep forms count a final line that has no newline. The last one counts only matching lines.
wc -l file.txt
wc -l < file.txt
awk 'END { print NR }' file.txt
grep -c '' file.txt
grep -c 'ERROR' file.txt
The rest of this guide explains why those answers can differ and how to pick one. To follow along, the guide on creating a file in Linux shows how to make sample files.
What wc -l counts
wc counts newline characters (the 0x0a byte that ends a line in a text file). It never parses records. A file with four newline-terminated lines returns 4. A blank line counts like any other because it still ends in a newline.
The tool is old. Wikipedia dates its first release to November 3, 1971 and credits the GNU version in coreutils to Paul Rubin and David MacKenzie. Because it is that old, every Unix-like system you touch ships some version of it.
Syntax and options for wc
The basic form is wc [OPTION]... [FILE].... With no option, wc prints lines, words and bytes in that order, followed by the file name. The options below narrow that output to what you need.
- -l, –lines: print the newline count
- -w, –words: print the word count
- -c, –bytes: print the byte count
- -m, –chars: print the character count, which differs from bytes on multibyte text
- -L, –max-line-length: print the length of the longest line (a GNU extension)
- –total=only: print only the total row when you pass several files
- –files0-from=FILE: read NUL-separated file names from FILE, or from standard input when FILE is a dash
GNU wc prints its columns in a fixed order whatever order you give the flags. wc -lw and wc -wl produced identical output on my machine. IBM’s AIX documentation says flag order decides output order so on GNU systems read columns by position or use one flag.
Count lines in Linux with wc
wc is the default choice because it does almost no parsing. The variations below cover the cases I reach for most often, from a bare number for a script to a total across a whole file list.
Count one file and drop the filename
Pass a path and wc prints the count followed by the name. Redirect the file into standard input (the stream a command reads when it gets no file argument) and wc has no name to print. That bare number suits scripts.
wc -l logs.txt
wc -l < logs.txt
count=$(wc -l < logs.txt)
On my four-line test file the first command printed 4 logs.txt and the second printed 4. The third stores the number in a Bash variable so a script can compare it against a limit.
Count several files and get a total
Give wc several paths and it prints one row per file plus a total row. GNU wc also accepts --total=only, which prints just the sum. Put -- before any name that starts with a dash so wc reads it as a path.
wc -l logs.txt partial.txt
wc -l --total=only logs.txt partial.txt
wc -l -- -notes.txt
With logs.txt and partial.txt the first command gave 4 and 1 with a total of 5. The second printed only 5, which is handy when a script needs one number for many files.
Count lines from a pipe
With no file argument wc reads standard input so any command can feed it. This is also why a bare wc -l at the prompt seems to hang. It waits for input until you press Ctrl+D.
printf 'alpha\nERROR disk\nERROR disk\n' | wc -l
ls -q | wc -l
ls | wc -l overcounts when a filename contains a newline. A test directory with four entries, one holding a newline in its name, printed 5 while ls -q printed 4. The guide on how to count files in a directory covers other ways to count entries.
Read file names from a list
GNU wc can read NUL-separated names (names ended by a zero byte instead of a newline) from a file or from standard input. That avoids argument-length limits and odd filenames while still printing a single total.
find sub -name '*.log' -print0 | wc -l --files0-from=-
- find sub: start in the sub directory
- -name ‘*.log’: match names that end in .log
- -print0: end each name with a zero byte
- –files0-from=-: make wc read those names from standard input
On my test tree that printed 1 sub/b.log and 2 sub/a.log with a total of 3. A plain find piped to xargs wc -l can split a long list into batches and print several total rows.
Count lines in compressed files
Decompress to a pipe so the archive stays compressed on disk. The -d flag decompresses and -c writes the result to standard output. zcat does the same thing in one word.
gzip -dc logs.txt.gz | wc -l
zcat logs.txt.gz | wc -l
Both commands printed 4 for my compressed copy of logs.txt. The same pattern works with xz -dc and bzip2 -dc. The gzip command guide covers the compression side and the remaining flags.
Count lines that match a pattern
Count matching lines with grep
grep -c prints how many lines match. A line counts once even if the pattern appears several times on it. Add -v to invert the test and count the lines that do not match.
grep -c 'ERROR' logs.txt
grep -vc 'ERROR' logs.txt
My four-line test file returned 2 for both since two lines held ERROR and two did not. Quote the pattern so the shell passes it through untouched, which matters for patterns with asterisks or dollar signs.
Count every match instead of lines
grep -o prints each match on its own line so piping it to wc -l counts occurrences. In my test file one line held ERROR twice. grep -c reported 2 and the pipeline reported 3.
grep -o 'ERROR' occ.txt | wc -l
Count blank and nonblank lines
Count nonblank lines
My test file has four lines: a, an empty line, a line of spaces and b. Every command below returned 2 because only a and b hold visible text. They differ in how they spot the blank lines.
grep -c '[^[:space:]]' blanks.txt
grep -cve '^\s*$' blanks.txt
awk 'NF' blanks.txt | wc -l
- -c: print a count instead of the matching lines
- -v: invert the match so blank lines are skipped
- -e: mark the next argument as the pattern
The first grep looks for any non-space character. The second inverts a blank-line match (\s is a GNU extension). awk uses NF, the field count, which is zero on blank lines.
Count blank lines only
Flip the test to count only blank and whitespace-only lines. This pattern matches lines that hold nothing but spaces, tabs or no characters at all. On the test file it returned 2.
grep -c '^[[:space:]]*$' blanks.txt
Why nl skips blank lines
nl numbers only nonempty lines by default so nl blanks.txt | tail -1 ended at 3 on my test file. Adding -ba numbers every line and ended at 4. cat -n numbers every line too but printing a whole file just to read the last number wastes time.
nl blanks.txt | tail -1
nl -ba blanks.txt | tail -1
Count lines with awk, sed and Bash
awk and the NR variable
awk splits input into records (newline-separated chunks by default) and keeps the running total in NR. The END block runs after the last record so printing NR there gives the line count.
awk 'END { print NR }' file.txt
Because awk counts records, it includes a final line with no newline. I tested this with mawk 1.3.4, the awk that Ubuntu installs by default. gawk follows the same POSIX record rules.
sed and the dollar address
In sed, $ addresses the last line and = prints a line number. With -n turning off normal output, sed -n '$=' prints one number: the number of the last line.
sed -n '$=' file.txt
sed -n '=' file.txt | tail -1
The second form prints a number for every line first so it is slower on large files. One gotcha is the empty file. wc and awk print 0 there. sed prints nothing at all.
A Bash loop for per-line logic
Use a Bash loop only when each line needs its own test or action. The || [[ -n $line ]] part keeps a final line that has no newline because read returns a failure status on that line even though it filled the variable.
count=0
while IFS= read -r line || [[ -n $line ]]; do
count=$((count + 1))
done < "$1"
printf '%s\n' "$count"
On the first 1,000,000 lines of a test file, cut with head, this loop needed about 3.7 seconds so wc is the better tool for plain counting. The while loop guide explains the loop syntax in detail.
Other one-liners with perl and pr
Perl counts records in its $. variable so perl -lne 'END { print $. }' file.txt printed 2 on the unterminated sample. pr -tn numbers lines without headers and tail -1 shows the last. They work but offer nothing wc and awk do not.
Count a final line with no newline
A file made with printf 'one\ntwo' ends without a newline. The table shows how each tool counts it. Only wc disagrees because it counts terminators while the others count lines.
- wc -l partial.txt: printed 1 because it counts newline characters
- awk ‘END { print NR }’ partial.txt: printed 2 because it counts input records
- grep -c ” partial.txt: printed 2 because it counts lines
- sed -n ‘$=’ partial.txt: printed 2 because it prints the number of the last line
A one-line file with no newline shows the gap clearly: wc -l printed 0 for the text solo and awk printed 1. The Perl FAQ treats a file lacking a final newline as malformed text.
Check the last byte
tail -c 1 prints the final byte and od -An -t x1 shows it in hexadecimal. A value of 0a means the file ends with a newline. Anything else means the last line has no terminator.
tail -c 1 partial.txt | od -An -t x1
- tail -c 1: output only the last byte
- od -An: dump the byte without an address column
- -t x1: print each byte as one hexadecimal value
On my samples partial.txt gave 6f, which is the letter o. logs.txt gave 0a. That matches the wc and awk results above so only the first file lacks a terminator.
Add the missing newline
GNU sed can append a final newline only when one is missing, using sed -i -e '$a\' file.txt. The -i flag edits in place so test on a copy first.
sed -i -e '$a\' file.txt
On a copy of partial.txt wc -l went from 1 to 2. On a copy of logs.txt cmp reported no change so files that already end in a newline are left alone.
Count lines in CRLF and CR-only files
Windows files end lines with CR plus LF (carriage return and line feed). wc counts the LF so CRLF files give the right number. A two-line CRLF test file returned 2.
Files that use only CR, as classic Mac OS did, contain no LF at all. wc -l printed 0 for a three-line sample. Set awk’s record separator to CR or translate CR to LF first. Both gave 3.
awk -v RS='\r' 'END { print NR }' cr.txt
tr '\r' '\n' < cr.txt | wc -l
Count rows in a CSV file
wc -l counts physical lines so it overcounts a CSV file when a quoted field contains a newline. My three-row test file (a header and two records, one record spanning two lines) returned 4 from wc -l.
wc -l < t.csv
python3 -c 'import csv,sys; print(sum(1 for _ in csv.reader(open(sys.argv[1], newline=""))))' t.csv
Python’s csv module parses quoting properly and counted 3 rows including the header. Subtract 1 for the header. For plain CSV files with no embedded newlines, wc -l minus one is fine and faster.
Count lines in a Bash variable or array
A command substitution strips trailing newlines so the count depends on how you pass the value on. With a three-line variable, wc -l <<< "$v" printed 3 while printf '%s' "$v" | wc -l printed 2.
v=$(printf 'a\nb\nc\n')
wc -l <<< "$v"
printf '%s\n' "$v" | wc -l
mapfile -t arr < logs.txt
echo "${#arr[@]}"
The here-string and the printf '%s\n' form add a newline back and give 3. mapfile reads a file into an array and ${#arr[@]} counts its entries, which matched awk’s record count in my tests but loads the whole file into memory.
Count lines across many files
wc -l *.log gives one row per file and a total. For a whole tree, feed find or git into wc with NUL-separated names so spaces and newlines in paths cannot break the count.
wc -l *.log
find . -type f -name '*.log' -print0 | wc -l --files0-from=-
git ls-files -z | wc -l --files0-from=-
for f in *.log; do printf '%s %s\n' "$(wc -l < "$f")" "$f"; done
The git form counts only tracked files, which usually keeps build output and node_modules out of the total. When a script needs each count separately, a for loop in Bash does the job.
Count lines of code with cloc
wc counts physical lines. cloc splits them into blank, comment and code lines per language, which suits questions about project size. On Debian and Ubuntu install it with sudo apt install cloc.
cloc my_project/
cloc --by-file my_project/
cloc project.zip
docker run --rm -v $PWD:/tmp aldanial/cloc
Point cloc at a directory or a single file. The --by-file option prints one row per file and cloc can also read archives such as a zip file. If your distro package is old, the project publishes a Docker image that runs the current version.
Which command is fastest on a large file
I generated a 20-million-line file (162 MB) with seq and timed each command on a warm cache. The sandbox runs coreutils 9.4 and mawk 1.3.4 so read the numbers as relative. Times are typical of repeated runs.
- wc -l big.txt: about 0.03 s
- awk ‘END { print NR }’ big.txt: about 0.3 s
- sed -n ‘$=’ big.txt: about 0.6 s
- perl -lne ‘END { print $. }’ big.txt: about 1.5 s
- Bash while read loop: about 3.7 s, measured on the first 1 million lines only
wc wins because it only has to find newline bytes. GNU coreutils keeps tuning it. The 9.6 notes say wc now reads at least 256 KiB at a time, which sped up wc -l by about 10% on cached files.
Release 9.9 made wc -l about 10% faster on AVX512 systems. Coreutils 9.11, released in April 2026, made it up to 4.5 times faster on Arm CPUs with NEON and wc -m up to 2.6 times faster on multibyte text. My 9.4 numbers predate all of that.
Piping through cat took about 0.125 s against 0.03 s for a direct read so prefer wc -l < file on big files. The extra time comes from copying every byte through a pipe.
Common mistakes when you count lines
Most surprising counts trace back to a short list of causes. Each one below showed up in my own tests. The fix for each is one flag or a different command.
- Running
wc -lwith no file argument waits on standard input. Press Ctrl+D or pass a file. grep -c '\n'does not count lines. In GNU grep 3.11 the\nmatches the letter n so it returned 1 on a two-line file where one line held an n.ls | wc -lovercounts when a name contains a newline. Usels -qor find.sed -n '$='prints nothing on an empty file where wc and awk print 0.wc -land awk NR differ by one when the last line has no newline.
Which method to use
Start from your goal and pick the matching row. When two tools disagree, check the last byte before you trust either one. I ran every row except the cloc one on my own test files.
- Bare number for a script:
wc -l < file - Count with the filename or several files:
wc -l file1 file2 - Last line may lack a newline:
awk 'END { print NR }' file - Only lines that match:
grep -c 'pattern' file - Every occurrence of a pattern:
grep -o 'pattern' file | wc -l - Only nonblank lines:
grep -c '[^[:space:]]' file - Compressed file:
gzip -dc file.gz | wc -l - Many files with odd names:
find . -print0 | wc -l --files0-from=- - Code, comment and blank lines:
cloc --by-file dir/
Key takeaways
wc -lcounts newline characters, not records.- Use
wc -l < filefor a bare number. awk 'END { print NR }'counts a last line with no newline.grep -ccounts matching lines andgrep -opiped to wc counts matches.gzip -dc file.gz | wc -lcounts compressed files without unpacking.wc -l --files0-from=-handles long lists and odd filenames.sed -n '$='prints nothing on an empty file.tail -c 1shows the last byte when counts differ.- cloc splits code, comment and blank lines.
Frequently asked questions
How do I count lines in a file in Linux?
Run wc -l filename to print the line count with the filename, or wc -l < filename for the bare number. Both count newline characters in the file.
Why does wc -l show one fewer line than I expected?
The last line has no trailing newline and wc counts newline characters. Use awk 'END { print NR }' file or grep -c '' file to include it.
How do I count lines that contain a word?
Use grep -c 'word' file. It counts matching lines so a line with the word twice counts once. Add -v to count lines without the word.
How do I count lines in a gzip file without extracting it?
Run gzip -dc file.gz | wc -l or zcat file.gz | wc -l. The archive stays compressed on disk and only the line count is printed.
How do I count nonblank lines in Linux?
Run grep -c '[^[:space:]]' file. It counts lines that contain at least one non-space character and skips empty or whitespace-only lines.
What is the fastest way to count lines in a huge file?
wc -l file is the fastest in my tests, about 0.03 seconds for 20 million lines. awk took about 0.3 seconds and sed about 0.6.
Does wc -l count blank lines?
Yes. A blank line still ends in a newline character so wc -l counts it. Use grep -c '[^[:space:]]' to count only nonblank lines.
Conclusion
To count lines in Linux, use wc -l < file for a bare number. It counts newline characters so awk 'END { print NR }' file is the safer choice when the last line may lack a newline. Add grep -c for matching lines and gzip -dc for compressed files.
