Table of Contents
Bash Deep Dive
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.
Before we get to the methods, here are three things that will silently give you the wrong answer:
# Bug 1: empty variable
var=""
echo "$var" | wc -l
# Bug 2: file without a trailing newline
printf "line1\nline2" > test.txt
wc -l < test.txt
# Bug 3: filenames with spaces
find . -name "*.txt" | xargs wc -l
If any of those surprised you, this guide is for you. If none of them surprised you, the advanced section at the end still has something new.
The practical question "how to count lines in Bash" splits into five different jobs:
- Count lines in a file.
- Count lines in a Bash variable.
- Count lines in command output.
- Count lines of code in a directory.
- Use the result safely inside a script.
If you only need a quick result, the browser-based Line Counter is easier than opening a terminal. If you need count lines in Bash behavior that survives edge cases, stay here.
If you are counting lines in Bash for a script, a one-off shell session, or a project report, the methods below separate those cases instead of pretending they all behave the same.
30-Second Cheat Sheet
Copy this first
This is the shortest honest answer to "count lines in Bash": use wc -l for files, grep -c '' for variables, wc -l again for command output, and find -print0 | xargs -0 wc -l for directory scans. The rest of this article explains why each one can fail.
If you only remember one thing about count lines in Bash, remember that the command changes slightly depending on whether the input is a file, a variable, or a pipeline.
Part 1: Count Lines in a File
Part 1 conclusion
For normal text files, wc -l is the default answer. It is fast, simple, and available on Linux, macOS, and most Unix-like systems.
Method 1A: Basic wc -l
wc -l file.txt
This is the classic Bash count lines in file command. The output usually includes both the number and the filename:
42 file.txt
That is fine for humans, but not ideal when you want the value in a script. If you want only the count, use input redirection:
wc -l < file.txt
That is the cleaner pattern for bash count lines in file when the result needs to be stored, compared, or passed to another command.
For most count lines in Bash tasks, this is the version you want in scripts.
Method 1B: The trailing newline trap
wc -l counts newline characters, not logical lines.
printf "line1\nline2\n" > with_newline.txt
printf "line1\nline2" > without_newline.txt
wc -l < with_newline.txt
wc -l < without_newline.txt
On the second file, wc -l returns 1 because there is only one newline character. If you care about the last unterminated line, use one of these instead:
awk 'END{print NR}' without_newline.txt
grep -c '' without_newline.txt
| Input | wc -l | awk 'END{print NR}' | grep -c '' |
|---|---|---|---|
line1\nline2\n | 2 | 2 | 2 |
line1\nline2 | 1 | 2 | 2 |
"" | 0 | 0 | 0 |
"\n" | 1 | 1 | 1 |
If you are wondering why bash wc -l not working sometimes means "my file looks like it has two lines but the number is one," this is usually the reason.
That trailing newline rule explains most of the weird results people hit when they count lines in Bash against generated text.
Method 1C: Multiple files
wc -l *.txt
That prints a count for each file and a final total. For a quick human scan, it is convenient. For scripts, the extra filename column is usually annoying.
If you only need a total across many files, you can sum them with a pipeline:
cat *.txt | wc -l
But cat is not safe when filenames may contain spaces or line breaks. For robust directory work, jump to Part 4.
Part 2: Count Lines in a Bash Variable
Part 2 conclusion
If the text lives in a variable, use printf instead of echo. echo adds its own newline and can distort empty values.
This is the part of count lines in Bash that most tutorials oversimplify.
It is the first place where count lines in Bash stops being a one-liner and becomes a real edge-case problem.
Method 2A: Count all lines in a variable
var="line1
line2
line3"
printf '%s' "$var" | grep -c ''
That returns 3 for a three-line variable and 0 for an empty variable.
Why not echo "$var" | wc -l?
empty=""
echo "$empty" | wc -l
That returns 1, because echo prints a newline even when the variable is empty. This is the hidden bug behind many bash count lines in variable snippets.
It is also why a lot of "count lines in Bash" answers look correct until the empty-string case shows up.
Method 2B: Count only non-empty lines
printf '%s' "$var" | grep -c .
Use this when you want line count with blank lines ignored. That is different from total line count:
grep -c ''counts every line.grep -c .counts only lines that contain at least one character.
That distinction matters when the variable contains blank separators or imported text.
Method 2C: Pure shell alternative
If you need a shell-only approach for Bash scripts, you can also read the variable through a loop:
count=0
while IFS= read -r line; do
count=$((count + 1))
done <<EOF
$var
EOF
This is slower than grep or awk, but it is explicit and easy to reason about in small scripts.
Part 3: Count Lines in Command Output
Part 3 conclusion
For pipelines, wc -l is still the right answer most of the time. The main job is making sure the command before it does not count itself or hide the real result.
The canonical pattern for bash count lines in output is:
some_command | wc -l
Examples:
ls -la | wc -l
grep -r "TODO" ./src | wc -l
ps aux | grep nginx | wc -l
The ps example has a classic trap: grep matches itself. Use a bracket trick to avoid that:
ps aux | grep "[n]ginx" | wc -l
For command output, grep -c is often better than grep | wc -l because it avoids a second process:
grep -c "ERROR" logfile.txt
That is one of the few Bash counting cases where the specialized command is both shorter and faster.
If your goal is to count lines in Bash output from a command you do not control, piping into wc -l is still the simplest reliable starting point.
Part 4: Count Lines of Code in a Directory
Part 4 conclusion
For count lines of code Bash workflows, protect filenames first. Once spaces are safe, choose whether you want raw line breaks or project metrics.
This is where find . | xargs wc -l goes wrong.
Method 4A: The unsafe version
find . -name "*.txt" | xargs wc -l
If a filename contains spaces, xargs splits it into multiple arguments. In a real project, that is enough to turn one file into two fake paths and crash the command.
Method 4B: The safe version
find . -name "*.txt" -print0 | xargs -0 wc -l
That null-separated form is the correct default for find + xargs when filenames are not guaranteed to be simple. It is the usual fix for find xargs wc -l failures.
Method 4C: Git-tracked files only
git ls-files -z '*.js' '*.ts' | xargs -0 wc -l
This is a strong pattern when you want count lines of code bash behavior that respects .gitignore and skips generated folders automatically. If your project is already in Git, this is often easier than hand-writing exclude rules.
For count lines in Bash workflows that scan a repository, git ls-files -z | xargs -0 wc -l is often the best balance of safety and simplicity.
Method 4D: Project metrics
If you care about code lines, comments, and blank lines rather than raw line breaks, use a dedicated tool:
cloc .
tokei .
Those tools are better than Bash one-liners for project reporting because they know about language syntax, comments, and folder exclusions.
If you need an editor-side workflow too, see count lines in VS Code. If you are scripting the same task in JavaScript, the JavaScript guide shows how to handle files, streams, and browser uploads.
Directory comparison
| Method | Handles spaces | Respects Git | Comments/blanks | Best for |
|---|---|---|---|---|
| `find . | xargs wc -l` | No | No | No |
| `find -print0 | xargs -0 wc -l` | Yes | No | No |
| `git ls-files -z | xargs -0 wc -l` | Yes | Yes | No |
cloc . | Yes | Yes | Yes | Code metrics and reports |
tokei . | Yes | Yes | Yes | Fast project summaries |
Part 5: Use Line Counts in Scripts
Part 5 conclusion
In scripts, the job is not just counting lines in Bash. The job is turning that count into a stable numeric value for comparisons and logging.
Store the count
lines=$(wc -l < file.txt)
echo "File has $lines lines"
This is the standard way to use wc -l in a Bash variable.
It is the pattern most scripts need when they count lines in Bash and then compare the result numerically.
If you want to normalize any padding from wc -l, use arithmetic expansion:
lines=$(( $(wc -l < file.txt) ))
That is a safe bash count lines in file pattern for numeric comparisons.
Compare the count
MAX_LINES=100
lines=$(wc -l < file.txt)
if [ "$lines" -gt "$MAX_LINES" ]; then
echo "File is too long"
fi
Wrap it in a function
count_lines() {
local target=$1
if [ ! -f "$target" ]; then
echo 0
return
fi
awk 'END{print NR}' "$target"
}
count_lines file.txt
This function is accurate for the trailing-newline edge case and easy to drop into a script.
Watch out for wc -l formatting
count=$(wc -l < file.txt)
printf '[%s]\n' "$count"
If your wc output looks padded in the terminal, arithmetic expansion or awk will normalize it. In shell scripts, if [ "$count" -gt 100 ] is the more important test anyway, because the numeric comparison ignores leading whitespace.
That keeps count lines in Bash from turning into a string-formatting problem.
Part 6: Advanced Cases
Count non-empty lines
grep -c . file.txt
This is the fastest way to count only non-empty lines in Bash.
If you want a whitespace-aware version, use:
grep -cv '^[[:space:]]*$' file.txt
Count matched lines
grep -c "ERROR" logfile.txt
grep -cv "DEBUG" logfile.txt
grep -c "^#" script.sh
These are the cases where wc -l is the wrong tool. You are no longer counting raw lines; you are counting lines that match a pattern.
Monitor a file in real time
watch -n 1 'wc -l < /var/log/app.log'
This is useful for logs that grow over time. If you need alerting, wrap the count in a loop and compare it to a threshold.
Performance comparison
On a Linux machine with an ext4 SSD, the relative ordering usually looks like this:
| Method | Speed | Memory | Notes |
|---|---|---|---|
wc -l | Fastest | Tiny | Best raw line counter |
awk 'END{print NR}' | Very fast | Tiny | Safer for unterminated last lines |
grep -c '' | Fast | Tiny | Counts all lines |
grep -c . | Fast | Tiny | Counts non-empty lines |
sed -n '$=' | Moderate | Tiny | Returns last line number only |
while read loop | Slow | Tiny | Fine for tiny scripts, bad for big files |
For bash count lines in output and file processing, that ranking is usually stable enough to make a decision without micro-optimizing.
When you count lines in Bash for automation, choose the simpler command first and only move to a slower loop when the input shape forces it.
FAQ
How do I count lines in a file in Bash?
Use wc -l < file.txt. That gives you a bare number, which is easier to use in scripts than wc -l file.txt, which also prints the filename.
Why does wc -l return the wrong count?
Because wc -l counts newline characters, not logical lines. A file without a trailing newline can be off by one. Use awk 'END{print NR}' if that edge case matters.
How do I count lines in a Bash variable?
Use printf '%s' "$var" | grep -c '' for total lines or printf '%s' "$var" | grep -c . for non-empty lines. Do not use echo for empty values, because it adds a newline of its own.
How do I count lines of command output?
Pipe the output into wc -l, for example command | wc -l. If you are grepping process output, use a pattern like grep "[n]ginx" so you do not count the grep command itself.
How do I recursively count lines in a directory?
Use find ... -print0 | xargs -0 wc -l for raw line counts, or git ls-files -z | xargs -0 wc -l if you only want tracked files.
How do I use line count in an if statement?
Store the result in a variable and compare it numerically. if [ "$lines" -gt 100 ]; then ...; fi is the standard Bash pattern.
What is the fastest way to count lines in Bash?
For files, wc -l is usually fastest. For non-empty lines, grep -c . is the practical alternative.
How do I count non-empty lines in Bash?
Use grep -c . file.txt or grep -cv '^[[:space:]]*$' file.txt if you want to treat whitespace-only lines as empty too.
Related Guides and Tools
- AWK line counting guide for
NR,FNR,RS, and per-file counts. - count lines on Linux and Mac for broader file-counting workflows.
- count lines in JavaScript when the text lives in code, streams, or browser uploads.
- Java file line counting for
Files.lines(),BufferedReader, Spring Boot uploads, and JVM large-file benchmarks. - C line counting for
fread,mmap,read(), and the systems-side explanation of whywc -lis so fast. - Lua line counting for
io.lines, explicitio.open, LuaJIT and OpenResty runtime notes, and byte-scanning large files. - PHP file line counting for
file(),fgets,SplFileObject, andwc -lfrom PHP. - Ruby file line counting for
File.foreach, binary encoding mode, Rails uploads, and safewc -lcalls. - Go line counting for
bufio.Scanner, gzip logs, and concurrent directory scans. - Rust line counting for
BufReader,read_line, and fast byte scans. - R line counting for
readLines(),readr::read_lines(),R.utils::countLines(), and theincomplete final linewarning. - Perl line counting for
$.,close ARGV,IO::File, and safewc -lintegration. - count lines in VS Code for editor status bar, selection, and project-wide methods.
- Python line counting for file, string, and directory counting in Python.
- Line Counter tool for browser-based pasted text and file uploads.
Need a Quick Count Without the Terminal?
If you are working with text files, logs, or CSV data outside a shell environment, paste the content or upload the file to the Line Counter. It runs in the browser and handles raw line counts without shell quoting mistakes.
Quick CTA
Need to count lines without opening a terminal? Paste or upload the content in the Line Counter and get the result instantly.
Frequently Asked Questions
How do I count lines in a file in Bash?
Use wc -l < file.txt for a clean numeric result. Passing the filename directly also works, but it prints the filename too, which is less convenient in scripts.
Why does wc -l return the wrong count?
wc -l counts newline characters, not logical lines. A file that ends without a trailing newline can be off by one, so awk 'END{print NR}' is safer when that edge case matters.
How do I count lines in a Bash variable?
Use printf '%s' "$var" | grep -c '' if you want total lines, or printf '%s' "$var" | grep -c . if you only want non-empty lines. Avoid echo for empty variables because it adds its own newline.
How do I count lines of command output?
Pipe the command into wc -l, for example command | wc -l. If the command itself may match grep, use a safer pattern like grep '[n]ginx' to avoid counting grep itself.
How do I recursively count lines in a directory?
Use find with -print0 and xargs -0, or use git ls-files -z | xargs -0 wc -l for Git-tracked files. The null-separated form prevents failures when filenames contain spaces.
How do I use line count in an if statement?
Capture the count into a variable and compare it numerically, for example lines=$(wc -l < file.txt); if [ "$lines" -gt 100 ]; then ...; fi.
What is the fastest way to count lines in Bash?
For raw line counts on files, wc -l is usually the fastest and simplest. For files without a trailing newline, awk 'END{print NR}' is more accurate.
How do I count non-empty lines in Bash?
Use grep -c . file.txt to count only non-empty lines, or grep -cv '^[[:space:]]*$' if you want an explicit whitespace-aware filter.
Related Guides
18 min read
How to Count Lines in JavaScript: 6 Methods with Performance Benchmarks
Count lines in JavaScript strings, files, Node.js streams, and the browser. Includes real performance benchmarks, edge case handling, and a decision guide for every scenario.
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.
11 min read
How to Count Lines in a File in Lua (And the io.lines Version Trap Nobody Warns You About)
Count lines in a file in Lua — io.lines, io.open, and byte scanning. Covers io.lines close behavior across Lua 5.1 to 5.4, LuaJIT and embedded runtime patterns, and high-performance counting for large files.
13 min read
How to Count Lines in a File in C (And Why `fgetc` Is 9x Slower Than `fread`)
Count lines in a file in C — fgets, fread, mmap, and the large performance gap between them. Covers `wc -l` internals, Windows vs Linux portability, long-line traps, and production-ready counting patterns for large files.