Table of Contents
PHP Deep Dive
How to Count Lines in a File Using PHP (Three Methods, Three Traps)
Count lines in a PHP file — file(), fgets loop, SplFileObject, and shell exec. Covers the feof off-by-one trap, OOM from file(), and the fastest method for large files with benchmarks.
Fatal error: Allowed memory size of 268435456 bytes exhausted
(tried to allocate 440 bytes) in C:\process_txt.php on line 109
That error came from a familiar PHP line-counting pattern:
$lines = count(file($path)) - 1;
The file was hundreds of MB. file() tried to read every line into a PHP array, and the process hit memory_limit.
The common fix is a fgets loop. That fixes the memory problem, but a widely copied version introduces a different bug: it increments after a failed read at EOF. If the file ends with a newline, it can count one extra line.
This guide gives you the practical answer for php count lines work:
- use
file()only for small known files - use a corrected
fgetsloop when readability matters - use
freadplussubstr_count($chunk, "\n")for php count lines large file workloads - use
shell_exec('wc -l')only as an optional Unix fast path - use WordPress APIs when the file came from an upload
You will also see why count lines php snippets from old forum posts often disagree by one line, and why php file count lines memory failures show up first in CMS and upload workflows.
If you just need a count now, use the Line Counter tool. If you are writing PHP code, the details below are where the edge cases live.
Quick Method Guide
| I want to... | Use this | Main warning |
|---|---|---|
| Count a small file | count(file($path)) | loads the whole file |
| Stream a normal file | fixed fgets loop | do not use while (!feof()) incorrectly |
| Count a large file fast | fread chunks plus substr_count | count "\n", not PHP_EOL, for portable files |
| Use OOP style | SplFileObject | seek(PHP_INT_MAX) has edge cases |
| Use the fastest Linux path | wc -l via shell_exec | escape the path and handle missing trailing newline |
| Count a WordPress upload | WordPress-safe wrapper | check nonce, capability, file type, and file size |
For most php count lines in file code, the production default is the chunked fread version. It is cross-platform, memory-stable, and fast enough for logs, CSV exports, and upload validation.
Method 1: file() - Simple but Dangerous for Large Files
The shortest PHP answer is:
<?php
$lineCount = count(file('data.txt'));
With basic error handling:
<?php
function countLinesSmallFile(string $filePath): int
{
if (!file_exists($filePath)) {
throw new RuntimeException("File not found: $filePath");
}
$lines = file($filePath, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
throw new RuntimeException("Cannot read file: $filePath");
}
return count($lines);
}
The PHP manual describes file() as reading the entire file into an array. Each array element corresponds to one line. That makes it convenient, but it is the core php file count lines memory trap.
| File size | Use file()? | Why |
|---|---|---|
| Under 5MB | yes | simple and readable |
| 5MB to 50MB | maybe | depends on memory_limit and average line length |
| Over 50MB | avoid | PHP array overhead grows quickly |
| Hundreds of MB | no | likely OOM in shared hosting or WordPress |
The memory cost is more than the raw file size because PHP stores an array plus one string per line. A 100MB file can take far more than 100MB once split into array elements.
<?php
$before = memory_get_peak_usage(true);
$lines = file('large_file.txt');
$after = memory_get_peak_usage(true);
echo 'Peak delta: ' . (($after - $before) / 1024 / 1024) . " MB\n";
Use file() for small config files, small test fixtures, and scripts with known inputs. Do not use it as the default php count lines in file answer for user uploads.
That is the simplest rule for avoiding php file count lines memory incidents: if the file can be user-provided, generated by a job, or larger than you expected, do not turn it into a PHP array before counting.
Common flags:
<?php
// Remove line endings from each element.
$lines = file('data.txt', FILE_IGNORE_NEW_LINES);
// Skip empty lines.
$lines = file('data.txt', FILE_SKIP_EMPTY_LINES);
// Combine both flags.
$lines = file(
'data.txt',
FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
);
If your input is CSV, remember that raw line counting is not the same as row counting when quoted fields contain embedded newlines. Use fgetcsv() for record-aware CSV processing.
Method 2: fgets Loop - Streaming with an Off-by-One Trap
The streaming idea is right:
<?php
$handle = fopen('largefile.txt', 'r');
$lineCount = 0;
while (!feof($handle)) {
fgets($handle);
$lineCount++;
}
fclose($handle);
This is the classic fgets count lines php mistake. feof() does not mean "the next read will succeed." It reports EOF after the stream has reached EOF. The PHP manual's own fgets example increments only after fgets() returns a string.
Here is the fixed version:
<?php
function countLinesWithFgets(string $filePath): int
{
if (!file_exists($filePath)) {
throw new RuntimeException("File not found: $filePath");
}
$handle = fopen($filePath, 'r');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filePath");
}
$lineCount = 0;
try {
while (($line = fgets($handle)) !== false) {
$lineCount++;
}
if (!feof($handle)) {
throw new RuntimeException("Unexpected read error: $filePath");
}
} finally {
fclose($handle);
}
return $lineCount;
}
This fixed fgets count lines php version handles:
- empty files: returns
0 - files with a trailing newline: no extra line
- files without a trailing newline: counts the last line
- large files: keeps only one line in memory
Use fgets when you need text-line semantics, validation per line, or simple code. For pure line counting, it still allocates one PHP string per line, so it is slower than chunk scanning.
The short version for fgets count lines php code is: feof() can help detect a read error after the loop, but fgets() must decide whether a line was actually read.
Method 3: fread Chunks - Best Pure PHP Large-File Default
When you only need a count, do not create one PHP string per line. Read fixed-size chunks and count newline bytes.
<?php
function countLinesFast(string $filePath): int
{
$handle = fopen($filePath, 'rb');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filePath");
}
$lineCount = 0;
$lastByte = '';
$sawData = false;
try {
while (!feof($handle)) {
$chunk = fread($handle, 65536);
if ($chunk === false) {
throw new RuntimeException("Cannot read file: $filePath");
}
if ($chunk === '') {
continue;
}
$sawData = true;
$lineCount += substr_count($chunk, "\n");
$lastByte = $chunk[strlen($chunk) - 1];
}
} finally {
fclose($handle);
}
if ($sawData && $lastByte !== "\n") {
$lineCount++;
}
return $lineCount;
}
This is the best pure PHP answer to php count lines large file for most production code:
- fixed memory usage
- no array of lines
- no one-string-per-line loop
- correct empty-file handling
- correct missing-trailing-newline handling
Why count "\n" instead of PHP_EOL?
For uploaded files, logs copied between machines, and files generated by other systems, count "\n".
$lineCount += substr_count($chunk, "\n");
Do not use this as your general-purpose counter:
$lineCount += substr_count($chunk, PHP_EOL);
PHP_EOL is the line ending of the server running PHP, not necessarily the line ending inside the file. A Windows CSV uploaded to a Linux PHP server contains "\r\n", and counting "\n" still returns the correct line-break count because each CRLF contains one LF.
This detail matters in count lines php code that runs in WordPress, shared hosting, CI, or Docker containers.
Method 4: SplFileObject - OOP Style with Quirks
SplFileObject gives PHP an object-oriented interface for files. A readable streaming version looks like this:
<?php
function countLinesSplReadable(string $filePath): int
{
$file = new SplFileObject($filePath, 'r');
$count = 0;
while (!$file->eof()) {
$line = $file->fgets();
if ($line === '' && $file->eof()) {
break;
}
$count++;
}
return $count;
}
There is also a famous SplFileObject count lines shortcut:
<?php
function countLinesSplSeek(string $filePath): int
{
if (filesize($filePath) === 0) {
return 0;
}
$file = new SplFileObject($filePath, 'r');
$file->seek(PHP_INT_MAX);
return $file->key() + 1;
}
The idea is:
SplFileObject::seek()moves to a zero-based line numberPHP_INT_MAXis larger than any real line numberkey()returns the current zero-based line number- adding
1turns the line number into a line count
This is compact, but not my default recommendation. It can be surprising on empty files and on files where a final newline produces a terminal empty line in the iterator model.
You may see examples that add flags:
<?php
$file = new SplFileObject($filePath, 'r');
$file->setFlags(
SplFileObject::READ_AHEAD
| SplFileObject::SKIP_EMPTY
| SplFileObject::DROP_NEW_LINE
);
Those flags are useful when iterating and intentionally skipping empty records. The PHP manual notes that SKIP_EMPTY requires READ_AHEAD to work as expected. But this is not a general line-count fix: it can skip empty lines in the middle of the file, and key() semantics are harder to reason about once iterator flags are involved.
If blank lines are meaningful, use fgets or fread instead. SplFileObject count lines code is fine for OOP codebases, but the chunk scanner is clearer for raw counts.
Use SplFileObject count lines patterns when they match the rest of your codebase. Do not use the shortcut just because it is shorter; the edge cases are less obvious than the fixed stream loop.
Method 5: shell_exec wc -l php - Fastest on Unix, Risky by Default
On Linux and macOS, wc -l is written in C and is usually faster than any PHP loop:
<?php
function countLinesShell(string $filePath): int
{
if (filesize($filePath) === 0) {
return 0;
}
$escaped = escapeshellarg($filePath);
$output = shell_exec("wc -l < $escaped");
if ($output === null || $output === false) {
throw new RuntimeException('shell_exec failed or is disabled');
}
$count = (int) trim($output);
$handle = fopen($filePath, 'rb');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filePath");
}
try {
fseek($handle, -1, SEEK_END);
$lastByte = fread($handle, 1);
} finally {
fclose($handle);
}
return $lastByte === "\n" ? $count : $count + 1;
}
There are two caveats.
First, never concatenate a user-controlled path into a shell command:
<?php
// Dangerous.
$count = shell_exec("wc -l " . $_POST['filename']);
Use escapeshellarg() for the path:
<?php
$escaped = escapeshellarg($filePath);
$count = shell_exec("wc -l < $escaped");
Second, wc -l counts newline characters. A non-empty file without a final newline has one logical line more than the raw wc -l value. That is why the function above checks the last byte.
Check availability before using shell_exec wc -l php code:
<?php
function isShellExecAvailable(): bool
{
if (!function_exists('shell_exec')) {
return false;
}
$disabled = array_map(
'trim',
explode(',', (string) ini_get('disable_functions'))
);
return !in_array('shell_exec', $disabled, true);
}
For hardened servers and shared hosts, assume shell_exec may be disabled. For Windows, prefer the pure PHP fread method.
Benchmark: All Methods Compared
These benchmark figures are representative for PHP 8.3 on Linux with an SSD and a 500MB text file of roughly 5 million lines. They are directional, not a universal promise; average line length, storage, CPU cache, and PHP configuration all matter.
| Method | Time | Peak memory | Security | Best use |
|---|---|---|---|---|
file() plus count() | about 3.2s | about 900MB | safe | small files only |
buggy fgets plus feof | about 4.8s | about 1MB | safe | avoid because wrong |
fixed fgets loop | about 4.8s | about 1MB | safe | readable streaming |
fread chunk scan | about 1.4s | about 64KB | safe | pure PHP large files |
SplFileObject | about 5.5s | about 1MB | safe | OOP style |
shell_exec('wc -l') | about 0.3s | very low | needs escaping | Unix fast path |
The practical conclusion:
- for php count lines in file examples under 10MB,
file()is acceptable - for php count lines large file code, prefer
freadchunk scanning - for Linux-only internal tools,
shell_exec wc -l phpcan be fastest - for upload flows, pick safety and predictable memory before speed
Part 6: Counting Lines in WordPress Uploads
WordPress changes the problem. You are not just counting a file; you are handling a user-uploaded attachment inside a permissioned application.
This php count lines wordpress example resolves an attachment path, validates access, checks the file extension and MIME mapping, chooses a memory-safe path, and returns a WordPress-style result.
<?php
/**
* Count lines in a WordPress uploaded TXT or CSV attachment.
*
* @return int|WP_Error
*/
function lc_count_uploaded_file_lines(int $attachmentId)
{
if (!current_user_can('edit_post', $attachmentId)) {
return new WP_Error(
'permission_denied',
__('You cannot access this attachment.', 'line-counter')
);
}
$filePath = get_attached_file($attachmentId);
if (!$filePath || !is_file($filePath)) {
return new WP_Error(
'file_not_found',
__('Uploaded file not found.', 'line-counter')
);
}
$allowedMimes = [
'txt' => 'text/plain',
'csv' => 'text/csv',
];
$fileType = wp_check_filetype($filePath, $allowedMimes);
if (!$fileType['type']) {
return new WP_Error(
'invalid_file_type',
__('Only TXT and CSV files are supported.', 'line-counter')
);
}
$fileSize = filesize($filePath);
if ($fileSize === false) {
return new WP_Error(
'size_error',
__('Cannot inspect uploaded file size.', 'line-counter')
);
}
$memoryLimit = wp_convert_hr_to_bytes(ini_get('memory_limit'));
$safeReadAllLimit = (int) ($memoryLimit * 0.25);
if ($fileSize > 0 && $fileSize < min(10 * 1024 * 1024, $safeReadAllLimit)) {
$lines = file($filePath, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
return new WP_Error(
'read_error',
__('Cannot read uploaded file.', 'line-counter')
);
}
return count($lines);
}
try {
return LineCounter::count($filePath);
} catch (RuntimeException $error) {
return new WP_Error('read_error', $error->getMessage());
}
}
add_action('wp_ajax_count_file_lines', function (): void {
check_ajax_referer('count_lines_nonce', 'nonce');
$attachmentId = absint($_POST['attachment_id'] ?? 0);
if (!$attachmentId) {
wp_send_json_error('Invalid attachment ID', 400);
}
$count = lc_count_uploaded_file_lines($attachmentId);
if (is_wp_error($count)) {
wp_send_json_error($count->get_error_message(), 400);
}
wp_send_json_success(['line_count' => $count]);
});
For stronger upload validation, use wp_check_filetype_and_ext() during the upload handling path. wp_check_filetype() is still useful here as a lightweight extension and MIME mapping check for an existing attachment.
This is the safe shape for php count lines wordpress plugins:
- never trust a raw
$_POST['path'] - resolve the file with
get_attached_file() - check
current_user_can() - verify the nonce with
check_ajax_referer() - avoid
file()unless the file is small relative tomemory_limit - fall back to a streaming counter
Part 7: A Production-Ready PHP Line Counter
This class chooses the simplest safe strategy:
- small files:
file()for clarity - large files on Unix with shell enabled:
wc -l, only when not skipping empty lines - everything else:
freadchunk scanning skipEmpty: streaming with chunk carry-over, so lines split across chunks are handled correctly
<?php
final class LineCounter
{
private const SMALL_FILE_BYTES = 10 * 1024 * 1024;
private const CHUNK_BYTES = 65536;
public static function count(
string $filePath,
bool $skipEmpty = false
): int {
if (!is_file($filePath)) {
throw new RuntimeException("File not found: $filePath");
}
$fileSize = filesize($filePath);
if ($fileSize === false) {
throw new RuntimeException("Cannot stat file: $filePath");
}
if ($fileSize === 0) {
return 0;
}
if (!$skipEmpty && $fileSize < self::SMALL_FILE_BYTES) {
$lines = file($filePath, FILE_IGNORE_NEW_LINES);
if ($lines === false) {
throw new RuntimeException("Cannot read file: $filePath");
}
return count($lines);
}
if (
!$skipEmpty
&& PHP_OS_FAMILY !== 'Windows'
&& self::isShellExecAvailable()
) {
return self::countWithShell($filePath);
}
return self::countWithStream($filePath, $skipEmpty);
}
private static function countWithShell(string $filePath): int
{
$escaped = escapeshellarg($filePath);
$output = shell_exec("wc -l < $escaped");
if ($output === null || $output === false) {
throw new RuntimeException('shell_exec failed');
}
$count = (int) trim($output);
$handle = fopen($filePath, 'rb');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filePath");
}
try {
fseek($handle, -1, SEEK_END);
$lastByte = fread($handle, 1);
} finally {
fclose($handle);
}
return $lastByte === "\n" ? $count : $count + 1;
}
private static function countWithStream(
string $filePath,
bool $skipEmpty
): int {
$handle = fopen($filePath, 'rb');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filePath");
}
$count = 0;
$lastByte = '';
$sawData = false;
$pending = '';
try {
while (!feof($handle)) {
$chunk = fread($handle, self::CHUNK_BYTES);
if ($chunk === false) {
throw new RuntimeException("Cannot read file: $filePath");
}
if ($chunk === '') {
continue;
}
$sawData = true;
$lastByte = $chunk[strlen($chunk) - 1];
if (!$skipEmpty) {
$count += substr_count($chunk, "\n");
continue;
}
$data = $pending . $chunk;
$parts = explode("\n", $data);
$pending = array_pop($parts);
foreach ($parts as $line) {
if (trim($line) !== '') {
$count++;
}
}
}
} finally {
fclose($handle);
}
if ($skipEmpty) {
return trim($pending) !== '' ? $count + 1 : $count;
}
return $sawData && $lastByte !== "\n" ? $count + 1 : $count;
}
private static function isShellExecAvailable(): bool
{
if (!function_exists('shell_exec')) {
return false;
}
$disabled = array_map(
'trim',
explode(',', (string) ini_get('disable_functions'))
);
return !in_array('shell_exec', $disabled, true);
}
}
Usage:
<?php
$total = LineCounter::count('data.txt');
$nonEmpty = LineCounter::count('config.txt', true);
PHP 8 named arguments also work:
<?php
$nonEmpty = LineCounter::count('config.txt', skipEmpty: true);
This class deliberately keeps shell usage out of the skipEmpty path. Counting only non-empty lines requires line awareness, so the stream path is safer than trying to build a portable shell pipeline.
Special Scenarios
Count uploaded CSV rows
If you need CSV rows rather than physical lines, use fgetcsv():
<?php
function countCsvRows(string $filePath): int
{
$handle = fopen($filePath, 'rb');
if ($handle === false) {
throw new RuntimeException("Cannot open file: $filePath");
}
$rows = 0;
try {
while (($row = fgetcsv($handle)) !== false) {
$rows++;
}
} finally {
fclose($handle);
}
return $rows;
}
This matters because CSV fields can contain embedded newlines inside quoted values.
Count standard input
<?php
$count = 0;
while (($line = fgets(STDIN)) !== false) {
$count++;
}
echo $count . PHP_EOL;
Count lines in a PHP string
<?php
function countLinesInString(string $text): int
{
if ($text === '') {
return 0;
}
$count = substr_count($text, "\n");
return str_ends_with($text, "\n") ? $count : $count + 1;
}
If you need PHP 7.4 compatibility, replace str_ends_with($text, "\n") with substr($text, -1) === "\n".
Decision Tree
How large is the file?
|
+-- Under 10MB
| +-- Use file() + count() if the input is trusted and bounded
|
+-- 10MB to 1GB
| +-- Need each line? fixed fgets loop
| +-- Need only the count? fread chunk scanning
|
+-- Over 1GB
| +-- Unix internal server? wc -l with escapeshellarg and newline fix
| +-- Shared host, Windows, WordPress, or user upload? fread chunk scanning
Do you need non-empty lines?
|
+-- Yes: stream with line carry-over, not raw substr_count only
+-- No: count "\n" bytes and fix the final unterminated line
PHP Version Compatibility
| Feature | Version support | Notes |
|---|---|---|
file() | PHP 4+ | Reads the whole file into an array |
fgets() and fread() | PHP 4+ | Safe streaming primitives |
SplFileObject | PHP 5.1+ | Convenient OOP file wrapper |
PHP_OS_FAMILY | PHP 7.2+ | Used for Unix versus Windows branching |
| Typed parameters and return types | PHP 7+ | Used in the examples |
str_ends_with() | PHP 8.0+ | Replace with substr($text, -1) === "\n" on PHP 7.4 |
| Named arguments | PHP 8.0+ | Optional; positional calls are shown first |
shell_exec() | PHP 4+ | Often disabled by hosting configuration |
The article targets PHP 7.4+ codebases, with PHP 8.3 used for the benchmark environment. When a PHP 8-only convenience appears, the PHP 7.4-compatible alternative is shown nearby.
Production Checklist
- Do not use
file()for unknown or large files. - Do not use
while (!feof($handle))as the counting condition. - Increment only after
fgets()returns a string. - Count
"\n"rather thanPHP_EOLfor cross-platform uploaded files. - Add one for a non-empty file that does not end with
"\n". - Guard empty files before
fseek($handle, -1, SEEK_END). - Escape shell arguments with
escapeshellarg()if you useshell_exec. - Check whether
shell_execis disabled. - In WordPress, use
get_attached_file()andcurrent_user_can(). - For CSV records, use
fgetcsv()instead of raw line counting.
Sources Checked
- PHP manual:
file()reads a whole file into an array - PHP manual:
fgets()returnsfalsewhen no more data is available - PHP manual:
feof()checks EOF state on a valid stream - PHP manual:
SplFileObject,seek(), andsetFlags() - PHP manual:
escapeshellarg()andshell_exec() - WordPress Developer Reference:
get_attached_file(),wp_check_filetype(),wp_convert_hr_to_bytes(),check_ajax_referer(),current_user_can(),wp_send_json_success(), andwp_send_json_error() - Stack Overflow context: the large-file PHP line-counting question includes the memory error pattern and community discussion around
fgets,feof, and thefile()OOM trap.
Related Guides and Tools
- wc -l on Linux and Bash
- Python line counting
- Ruby line counting
- Node.js line counting
- Java line counting
- Line Counter tool
Processing a CSV upload in PHP?
Before you write the import logic, check the line count first. Paste the file into the Line Counter. No PHP runtime, no memory limits, no off-by-one surprises.
Frequently Asked Questions
How do I count lines in a file in PHP?
For small files, count(file($path)) is concise. For production and large files, use fread chunks and count '\n' bytes, or use a fixed fgets loop when line semantics matter.
Why does count(file($path)) cause memory errors?
PHP file() reads the entire file into an array, so each line becomes a string element plus array overhead. Large logs or CSVs can exceed memory_limit.
How do I fix the off-by-one error in PHP line counting?
Do not write while (!feof($handle)) { fgets($handle); $count++; }. Instead, increment only when fgets($handle) !== false.
How do I count lines in a large file in PHP?
Use fread with a 64KB or 1MB buffer, substr_count($chunk, '\n'), and add one if the non-empty file does not end with a newline.
How do I use SplFileObject to count lines?
SplFileObject can stream lines or use the seek(PHP_INT_MAX) trick, but guard empty files and trailing newline behavior. It is convenient, not the fastest production default.
Is shell_exec safe for counting lines?
Only if shell_exec is enabled, the platform has wc, and the file path is escaped with escapeshellarg. Never concatenate user input directly into a shell command.
How do I count lines in PHP without loading the file?
Use fgets, SplFileObject, or fread chunk scanning. All three stream from disk instead of building a full array.
How do I count lines in WordPress?
Use get_attached_file to resolve the upload path, check permissions and nonce, validate the file type, then use a streaming line counter for large uploads.
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.
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.
13 min read
How to Count Lines in a File Using Ruby (And the Encoding Trap Nobody Warns You About)
Count lines in a file using Ruby — File.foreach, readlines, IO.read, and wc -l. Covers the invalid byte sequence trap, memory issues, and Rails-safe patterns with benchmarks.
16 min read
How to Count Lines in a File Using Java (6 Methods, Benchmarked)
Count lines in a file using Java — BufferedReader, Files.lines, LineNumberReader, BufferedInputStream, and more. Includes benchmark results for 5GB files and Java 8–17 examples.