Table of Contents
Back to Blog

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.

Bash 5.xzsh 5.xGNU and BSD wc
Published: May 12, 2026Updated: May 12, 202616 min readAuthor: Line Counter Editorial Team
BashShellLinuxmacOSTutorial

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
Inputwc -lawk 'END{print NR}'grep -c ''
line1\nline2\n222
line1\nline2122
""000
"\n"111

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

MethodHandles spacesRespects GitComments/blanksBest for
`find .xargs wc -l`NoNoNo
`find -print0xargs -0 wc -l`YesNoNo
`git ls-files -zxargs -0 wc -l`YesYesNo
cloc .YesYesYesCode metrics and reports
tokei .YesYesYesFast 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:

MethodSpeedMemoryNotes
wc -lFastestTinyBest raw line counter
awk 'END{print NR}'Very fastTinySafer for unterminated last lines
grep -c ''FastTinyCounts all lines
grep -c .FastTinyCounts non-empty lines
sed -n '$='ModerateTinyReturns last line number only
while read loopSlowTinyFine 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.

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