Skip to content

Lire un fichier ligne par ligne snippet

Itérer sur un fichier texte une ligne à la fois sans le garder en mémoire — la forme de tout scan de logs et de tout flux de batch.

Itérer sur un fichier texte une ligne à la fois sans le garder en mémoire — la forme de tout scan de logs et de tout flux de batch. Chaque écosystème cache un jumeau qui absorbe tout à côté de la primitive de streaming (readFileSync().split vs readline, ReadAllLines vs ReadLines, IO.readlines vs foreach), et choisir le mauvais transforme un log de plusieurs gigaoctets en kill par manque de mémoire. Aux extrémités : la plupart des APIs retirent le \n mais laissent un \r parasite des fichiers CRLF, et une dernière ligne sans retour à la ligne final arrive quand même comme une ligne. SQL est omis : le moteur ne peut lire que les disques de son propre serveur (pg_read_file de Postgres sous superuser), jamais le fichier de ta machine — c'est le travail du driver client.

Recette exécutable · 14 langages
Files & Datafilesiostreaming

Every language

14 langages, copy-ready. One at a time with syntax highlighting, or all inline.

JSJavaScript
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

const rl = createInterface({
  input: createReadStream('app.log'),
  crlfDelay: Infinity,
});

for await (const line of rl) {
  console.log(line);
}

readline over a createReadStream never holds the whole file — readFileSync('app.log', 'utf8').split('\n') is the memory bomb it replaces. crlfDelay: Infinity folds \r\n into one break so Windows logs don't leave a stray \r.

TSTypeScript
import { createReadStream } from 'node:fs';
import { createInterface } from 'node:readline';

const rl = createInterface({
  input: createReadStream('errors.log'),
  crlfDelay: Infinity,
});

let errors = 0;
for await (const line of rl) {
  if (line.startsWith('ERROR')) errors++;
}
console.log(`${errors} error line(s)`);

Same runtime as the JavaScript version — line is inferred as string, so no nullish handling is needed. The async iterator ends cleanly at EOF, including a final line with no trailing newline.

GoGo
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	f, err := os.Open("app.log")
	if err != nil {
		panic(err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
	}
	if err := scanner.Err(); err != nil {
		panic(err)
	}
}

Scan() strips \n and \r\n and yields lines up to 64 KiB by default — a longer line returns bufio.ErrTooLong, so call scanner.Buffer(make([]byte, 0, 64*1024), 1<<20) for ragged logs. Always check scanner.Err() after the loop; the for condition alone swallows read errors.

RsRust
use std::fs::File;
use std::io::{BufRead, BufReader};

fn main() -> std::io::Result<()> {
    let file = File::open("app.log")?;
    for line in BufReader::new(file).lines() {
        println!("{}", line?);
    }
    Ok(())
}

BufRead::lines strips \n and a trailing \r and yields io::Result per line — apply ? to each, since a mid-file read error surfaces exactly there. Without BufReader each line costs a syscall; File alone is unbuffered.

PHPPHP
<?php
$handle = fopen('app.log', 'r');
while (($line = fgets($handle)) !== false) {
    echo rtrim($line, "\r\n"), PHP_EOL;
}
fclose($handle);

fgets keeps the trailing newline and returns false at EOF — compare with !== false, because a last line of "0" loosely equals false and would end the loop a line early. file('app.log') loads every line into an array instead; rtrim removes the \n and the \r of CRLF.

PyPython
from pathlib import Path

with Path("app.log").open(encoding="utf-8") as f:
    for line in f:
        print(line.rstrip("\n"))

Iterating the file object streams through a buffered reader and speaks universal newlines — \n, \r\n, and a lone \r all arrive as \n (open with newline='' only for csv). read().split('\n') slurps and mishandles CRLF; the last line without a newline still arrives.

CC
#include <stdio.h>

int main(void) {
    FILE *f = fopen("app.log", "r");
    if (f == NULL) {
        perror("fopen");
        return 1;
    }

    char line[256];
    while (fgets(line, sizeof line, f) != NULL) {
        fputs(line, stdout); /* keeps the trailing '\n' */
    }
    fclose(f);
    return 0;
}

fgets stores at most sizeof line - 1 bytes and keeps the '\n' — a line longer than the buffer arrives split across several calls. It returns NULL at EOF and on error; ferror(f) tells them apart. For unbounded lines use POSIX getline(&buf, &cap, f), which allocates.

C++C++
#include <fstream>
#include <iostream>
#include <string>

int main() {
    std::ifstream in("app.log");
    std::string line;
    while (std::getline(in, line)) {
        std::cout << line << '\n';
    }
}

std::getline strips the '\n' delimiter but leaves a '\r' from CRLF files — trim before exact comparisons. ifstreams buffer by default, and the returned stream converts to false at EOF; std::istringstream over a slurped std::string is the non-streaming alternative.

C#C#
using System.IO;

foreach (string line in File.ReadLines("app.log"))
{
    Console.WriteLine(line);
}

File.ReadLines is the lazy IEnumerable<string> — File.ReadAllLines() loads every line into an array first and looks nearly identical at the call site. ReadLines decodes UTF-8, splits on \n and \r\n, and never yields a phantom empty line after a trailing newline.

JvJava
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.stream.Stream;

public class ReadLines {
    public static void main(String[] args) throws IOException {
        try (Stream<String> lines = Files.lines(Path.of("app.log"))) {
            lines.filter(line -> line.contains("ERROR"))
                 .forEach(System.out::println);
        }
    }
}

Files.lines streams lazily and decodes UTF-8 by default — Files.readAllLines slurps a List<String> first. The stream keeps the file open, so try-with-resources is not optional: skipping it leaks the descriptor until GC. Java 11+ for Path.of.

SwSwift
import Foundation

@main
struct ReadLines {
    static func main() async throws {
        let url = URL(fileURLWithPath: "app.log")
        for try await line in url.lines {
            print(line)
        }
    }
}

URL.lines (macOS 13 / iOS 16+) streams the file as an AsyncSequence of lines, and @main with an async main() makes the process await it — String(contentsOf:) + split is the slurping trap it replaces. On older runtimes, read Data chunks via FileHandle and split on \n yourself.

KtKotlin
import java.io.File

fun main() {
    File("app.log").useLines { lines ->
        lines.forEach(::println)
    }
}

useLines closes the handle when the block returns — the Sequence is lazy and single-shot, so consume it inside the block. readLines() is the array-slurping sibling; forEachLine { } is the inline shortcut over the same machinery.

RbRuby
File.foreach('app.log') { |line| puts line }

foreach yields one line (newline included) at a time in constant memory — IO.readlines('app.log') materializes the whole array first. Call line.chomp when the trailing "\n" would pollute a comparison or a hash key.

ZigZig
const std = @import("std");

pub fn main() !void {
    var file = try std.fs.cwd().openFile("app.log", .{});
    defer file.close();

    var buffered = std.io.bufferedReader(file.reader());
    const reader = buffered.reader();
    const stdout = std.io.getStdOut().writer();

    while (try reader.readUntilDelimiterOrEofAlloc(std.heap.page_allocator, '\n', 1024 * 1024)) |line| {
        defer std.heap.page_allocator.free(line);
        try stdout.print("{s}\n", .{std.mem.trimRight(u8, line, "\r")});
    }
}

Stdlib only, and File has no newline-aware helper: bufferedReader wraps the fd, then readUntilDelimiterOrEofAlloc allocates each line without the delimiter and returns null at EOF (ending the while). A '\r' before the '\n' is yours to trim, and the max-size argument turns a newline-free binary blob into error.StreamTooLong instead of an unbounded allocation.