mitmario.dev

Dateien lesen

Node.js Sandbox 3 Min Lesezeit 3 BeispieleLektion 1 von 7

Ab hier kann Node etwas, was der Browser nie können wird: an die Festplatte. Und ab hier bleibt zum ersten Mal etwas übrig, wenn dein Programm fertig ist.

Zwei Arten, dasselbe Modul zu benutzen

node:fs gibt es in zwei Ausführungen. Die ältere arbeitet mit Rückruffunktionen, die neuere mit Promises und heißt node:fs/promises. Es ist dasselbe Modul und dieselbe Funktionalität, nur die Schreibweise unterscheidet sich.

Wir nehmen in diesem Kurs durchgehend node:fs/promises, weil du await schon kennst und weil verschachtelte Rückruffunktionen ab drei Dateien unlesbar werden. In altem Code wirst du die Callback-Variante trotzdem sehen.

Daneben gibt es in node:fs zu jeder Funktion noch eine Variante mit Sync am Ende, also readFileSync statt readFile. Die wartet, bis die Datei da ist, und blockiert dabei alles andere. Warum das ein Problem ist, klärt Abschnitt 6.

Eine Datei einlesen
import { readFile } from "node:fs/promises";

const text = await readFile("liste.txt", "utf8");

console.log(text);
console.log("Zeichen:", text.length);
console.log("Zeilen:", text.trim().split("\n").length);

Mehr ist es nicht: Dateiname rein, Inhalt raus. Der Pfad wird relativ zum Arbeitsverzeichnis aufgelöst, nicht relativ zu deiner Datei. Das ist genau der Unterschied aus Lektion 2.4, und er trifft dich hier zum ersten Mal wirklich.

Der zweite Parameter, den man einmal vergisst

"utf8" sagt Node, wie es die Bytes in der Datei als Text deuten soll. Lässt du es weg, deutet Node gar nichts und gibt dir die Bytes selbst.

Ohne Kodierung kommt ein Buffer
import { readFile } from "node:fs/promises";

// Der zweite Parameter fehlt, deshalb kommen rohe Bytes zurück.
const roh = await readFile("liste.txt");

console.log("Roh:", roh);
console.log("Typ:", roh.constructor.name);
console.log("Als Text:", roh.toString("utf8").trim());

Ein Buffer ist eine Liste von Bytes, ausgegeben in Hexadezimalschreibweise. Du kannst darin sogar etwas erkennen: 4b ist das K von Kaffee, und 0a ist der Zeilenumbruch.

Verwirrend wird es dadurch, dass ein Buffer sich in vielen Situationen wie Text benimmt. Hängst du ihn an eine Zeichenkette, wandelt JavaScript ihn stillschweigend um. Rufst du .trim() darauf auf, gibt es einen Fehler, denn ein Buffer hat diese Methode nicht. Der Fehler kommt dann an einer ganz anderen Stelle als die vergessene Angabe.

Buffer sind kein Unfall, sondern der richtige Datentyp für alles, was kein Text ist: Bilder, PDFs, Videos. Abschnitt 7 nimmt sie sich richtig vor. Für Textdateien schreibst du "utf8" dazu, und zwar jedes Mal.

Was du mit dem Text danach machst

Eine Textdatei kommt als eine einzige lange Zeichenkette zurück, mit Zeilenumbrüchen darin. Der übliche erste Schritt ist deshalb Zerlegen.

Von Zeilen zu Daten
import { readFile } from "node:fs/promises";

const text = await readFile("preise.txt", "utf8");
const zeilen = text.trim().split("\n");

for (const zeile of zeilen) {
  const [name, preis] = zeile.split(";");
  console.log(`${name}: ${Number(preis).toFixed(2)} EUR`);
}

Beachte das trim() vor dem split(). Fast jede Textdatei endet mit einem Zeilenumbruch, und ohne trim() hättest du am Ende eine leere Zeile mit, die du beim Zählen nicht erwartest. Das ist eine der Kleinigkeiten, die einen Nachmittag kosten können.

Und wenn die Datei groß ist?

readFile holt die ganze Datei in den Arbeitsspeicher, bevor du die erste Zeile siehst. Bei einer Liste mit dreißig Einträgen ist das völlig egal.

Bei einer Logdatei mit zwei Gigabyte ist es das nicht. Dein Programm braucht dann zwei Gigabyte Speicher, und wenn es die nicht bekommt, stürzt es ab, ohne eine einzige Zeile verarbeitet zu haben.

Für diesen Fall gibt es Streams, also einen Weg, eine Datei stückweise durchlaufen zu lassen, ohne sie je vollständig zu halten. Abschnitt 7 baut das auf. Bis dahin gilt: readFile ist für Dateien, deren Größe du kennst und die in den Speicher passen.

Zum Mitnehmen

Ohne den zweiten Parameter bekommst du keinen Text zurück, sondern rohe Bytes. Beim Ausgeben sieht man den Unterschied sofort.

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.