Abschnitt 4 · Lektion 4
Eingaben lesen
Argumente kommen beim Start, Umgebungsvariablen kommen von außen. Es bleibt ein dritter Weg: Text, der während der Laufzeit hereinkommt. Die Standardeingabe, kurz stdin.
Sie ist das Gegenstück zu den beiden Kanälen aus Lektion 4.3. Dort ging etwas hinaus, hier kommt etwas herein, und es sind dieselben Rohre.
Es gibt zwei Arten, sie zu benutzen, und sie führen zu ganz verschiedenen Werkzeugen.
Der Weg mit der Frage
Wenn ein Mensch davorsitzt, willst du fragen können. Dafür gibt es node:readline, und in der
modernen Fassung mit Promises liest es sich wie normaler Code.
import { createInterface } from "node:readline/promises";
const rl = createInterface({
input: process.stdin,
output: process.stdout,
});
const name = await rl.question("Wie heisst du? ");
console.log(`Hallo, ${name}!`);
rl.close(); rl.question gibt ein Promise zurück, await wartet darauf. Erst wenn jemand etwas getippt und
Enter gedrückt hat, geht es weiter.
Auf dem Bildschirm stehen dann beide in einer Zeile: die Frage, die dein Programm geschrieben hat, und dahinter das, was der Mensch getippt hat. So sieht ein Terminal aus, es zeigt Eingetipptes einfach mit an.
Das rl.close() am Ende ist wichtiger, als es aussieht. Solange die Schnittstelle offen ist, wartet
Node weiter auf Eingaben, und dein Programm hört nicht auf. Das ist derselbe Mechanismus wie in
Lektion 1.2: Node beendet sich, wenn nichts mehr wartet. Ein offenes readline ist etwas, das
wartet.
Starte einmal die Sandbox und schau in das Terminal. Dieses Beispiel läuft dann, und was dort steht, ist der ganze Punkt dieser Lektion. Die Frage steht da, eine Antwort kommt nicht, und auf dem Fehlerkanal liegt eine Zeile, die man sonst selten zu sehen bekommt:
Warning: Detected unsettled top-level await at file:///vercel/sandbox/lern/fragen.mjs:8
Node sagt damit: Dieses await ist nie fertig geworden, und es wartet auch nichts mehr darauf, also
höre ich auf. Hier sitzt kein Mensch. Die Eingabe ist von vornherein zu Ende, rl.question bekommt
nie eine Antwort, und weil rl.close() hinter dem await steht, wird auch das nie erreicht. Kein
Absturz, keine Fehlermeldung, nur ein Programm, das auf etwas gewartet hat, das es hier nicht gibt.
Der Weg mit der Kette
Der zweite Fall kommt in der Praxis häufiger vor, und er sieht ganz anders aus. Jemand schiebt deinem Programm etwas hinein:
cat einkauf.txt | node zaehlen.mjs
Hier sitzt kein Mensch davor. Es gibt nichts zu fragen, es liegt einfach Text an, und dein Programm soll ihn verarbeiten.
let text = "";
for await (const stueck of process.stdin) {
text += stueck;
}
const zeilen = text.trim().split("\n");
console.log("Zeilen:", zeilen.length);
console.log("Erste:", zeilen[0]);
console.log("Letzte:", zeilen.at(-1)); process.stdin lässt sich mit for await durchgehen. Die Schleife bekommt den Text in Stücken, so
wie er ankommt, und endet, wenn nichts mehr kommt. Danach liegt alles in einer Variablen.
Für kleine Eingaben ist das genau richtig. Für eine sehr große Datei nicht, denn dann liegt sie komplett im Arbeitsspeicher. Wie man stattdessen stückweise arbeitet, ohne alles zu sammeln, ist das Thema von Abschnitt 7.
Läuft dieses Beispiel ohne Eingabe, steht im Terminal Zeilen: 1, und hinter Erste: und
Letzte: jeweils nichts. Eine Zeile, obwohl gar nichts hereinkam. Das ist keine Merkwürdigkeit der Sandbox, sondern von
split: Ein leerer Text zerfällt in genau ein Stück, nämlich das leere.
Und jetzt leite selbst etwas hinein. Das Terminal ist eine Shell, eine Pipe gehört zu dem Wenigen, was eine Shell wirklich gut kann, und dieselbe Maschine, die das Beispiel gerade gefahren hat, steht dir dort zur Verfügung. Hol dieses Beispiel nach vorn, starte die Sandbox und tipp:
printf 'Brot\nMilch\nKaese\n' | node zaehlen.mjs
Dann steht im Terminal Zeilen: 3, darunter Erste: Brot und Letzte: Kaese.
Das ist derselbe Code, dieselbe Datei, derselbe Rechner. Geändert hat sich nur, dass jetzt jemand etwas hineinschiebt.
Warum der zweite Weg meistens der bessere ist
Ein Werkzeug, das Fragen stellt, kann nur von einem Menschen benutzt werden. Sobald jemand es in ein Skript einbaut, hängt das Skript und niemand weiß warum.
Ein Werkzeug, das liest, was hereinkommt, und schreibt, was herauskommt, lässt sich beliebig
verketten. Genau darauf beruht die gesamte Unix-Werkzeugkiste: grep, sort, wc und die anderen
stellen keine Fragen, sie nehmen etwas entgegen und geben etwas zurück.
Die brauchbare Mischung: Nimm die Werte über Argumente und die Daten über stdin, und frag nur dann, wenn wirklich etwas fehlt. So funktioniert dein Werkzeug in beiden Welten.
Warum es zu dieser Lektion keine Aufgabe gibt
Diese Lektion hat keine Challenge, und der Grund gehört zum Stoff.
Der Prüfknopf startet deinen Code in einer Sandbox, und der Weg, auf dem er dorthin fährt, hat keine Eingabe: Dein Programm bekommt sie so, wie du es oben gesehen hast, nämlich sofort zu Ende. Eine Prüfliste könnte hier also nur abfragen, was dein Werkzeug ohne jede Eingabe tut, und das ist ausgerechnet der Fall, um den es in dieser Lektion nicht geht.
Im Terminal gibt es die Eingabe sehr wohl, sie kommt nur nicht von einer Tastatur, sondern aus einer Pipe. Deshalb steht der Handgriff weiter oben statt in einer Aufgabe: Er funktioniert, er wird nur nicht bewertet.
Was du mitnehmen sollst, ist ohnehin eine Entwurfsentscheidung und kein Handgriff: Frag dich bei jedem Werkzeug, ob es fragen muss oder ob es auch lesen könnte.
Zum Mitnehmen
Ein Werkzeug, das sich in eine Kette einbauen lässt, ist mehr wert als eins, das Fragen stellt.
Jetzt du
Basis Konto, kostenlosIm Editor änderst du die Beispiele dieser Lektion und lässt sie gleich laufen. So merkst du am schnellsten, ob es sitzt.
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 2 Beispielen zum Ausprobieren
Steht hier, ohne Konto lesbar.