Zum Inhalt springen
aviral gupta

// B3.4 · ca. 33 Min. · Einstieg

Ordner und stat

Nach dieser Lektion legen Sie Ordner an, listen sie auf und entfernen sie, lesen Größe, Typ und Zeit eines Eintrags mit stat und behandeln Ordnerfehler anhand ihres Codes.

Lektion 4 von 5 in B3 Dateien und Pfade

Danach können Sie

  • Ordner mit mkdir, readdir, rmdir und rm anlegen, auflisten und entfernen, und sie kopieren oder verschieben
  • Größe, Typ und Änderungszeit mit stat oder lstat lesen und Listen vor der Ausgabe sortieren
  • Die Operation versuchen statt vorher zu prüfen, und ENOENT, ENOTDIR, ENOTEMPTY und EEXIST behandeln
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen aus B3.2: docs ist ein Ordner, keine Datei. Was gibt dieses Programm aus?

    import {readFile} from 'node:fs/promises';
    
    try {
      await readFile('docs', 'utf8');
    } catch (error) {
      console.log(error.code);
    }
  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie es vorher, bevor Sie weiterlesen. Der Ordner docs enthält a.txt, Beta.md und c.txt. Was gibt dieses Programm aus?

    import {readdir} from 'node:fs/promises';
    
    const names = await readdir('docs');
    console.log(names.sort().join(', '));
  3. Üben · Aufgabe 3 von 7

    docs enthält die Datei a.txt und die Ordner img und notes. Ergänzen Sie die Option, sodass readdir Dirent-Objekte liefert und das Programm img, notes ausgibt.

    const entries = await readdir("docs", {____: true});
    console.log(entries.filter((e) => e.isDirectory()).map((e) => e.name).sort().join(", "));
    const entries = await readdir("docs", {: true});
  4. Üben · Aufgabe 4 von 7

    Der Ordner full enthält a.txt, und missing gibt es nicht. Ordnen Sie jedem Aufruf zu, was passiert.

  5. Üben · Aufgabe 5 von 7

    Was gibt dieses Programm aus?

    import {stat, writeFile} from 'node:fs/promises';
    
    const text = 'Grüße\n';
    await writeFile('greeting.txt', text);
    const info = await stat('greeting.txt');
    console.log(text.length, info.size);
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe, unter Linux oder macOS. link.txt ist ein symbolischer Link auf big.txt, die 1000 Bytes enthält. Was gibt dieses Programm aus?

    import {stat, symlink, writeFile} from 'node:fs/promises';
    
    await writeFile('big.txt', 'x'.repeat(1000));
    await symlink('big.txt', 'link.txt');
    const info = await stat('link.txt');
    console.log(info.isSymbolicLink(), info.size);
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Schreiben Sie main.js so, dass node main.js docs den Ordner docs auflistet: eine Zeile pro Eintrag, nach Namen sortiert, ein Ordner als notes/ und eine Datei mit ihrer Größe, als b.txt 4 bytes. Ein fehlender Ordner oder eine Datei statt eines Ordners gibt not a folder: und den Pfad mit seinem Code auf stderr aus und setzt den Exit-Code 1. Legen Sie einen Testordner an und probieren Sie alle drei Fälle.

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Ein Ordner von Anfang bis Ende

Dieses Programm baut neben main.js einen kleinen Ordnerbaum namens demo, betrachtet ihn auf drei Arten und entfernt ihn wieder. mkdir legt drei Ebenen auf einmal an und meldet den ersten Ordner, den es angelegt hat. readdir mit withFileTypes und stat beschreiben die oberste Ebene, nach Namen sortiert; readdir mit recursive listet jeden Pfad darunter. rmdir lehnt den Ordner notes ab, weil er nicht leer ist, rm mit recursive entfernt ihn, und stat mit throwIfNoEntry: false bestätigt, dass er weg ist. Das Programm gibt nie eine Zeit aus, also ist seine Ausgabe bei jedem Lauf dieselbe.

main.js

import {mkdir, readdir, rm, rmdir, stat, writeFile} from 'node:fs/promises';
import {join, relative} from 'node:path';

const root = join(import.meta.dirname, 'demo');
await rm(root, {recursive: true, force: true}); // start clean, even if demo is missing
const first = await mkdir(join(root, 'notes', '2026'), {recursive: true});
console.log('first folder created:', relative(import.meta.dirname, first));
await writeFile(join(root, 'todo.txt'), 'buy milk\n');
await writeFile(join(root, 'README.md'), '# Demo\n');
await writeFile(join(root, 'notes', '2026', 'jan.md'), 'Grüße\n');

// The top level: Dirent for the type, stat for the size. Sorted by name.
const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
for (const entry of (await readdir(root, {withFileTypes: true})).sort(byName)) {
  if (entry.isDirectory()) console.log(`  ${entry.name}/`);
  else console.log(`  ${entry.name} ${(await stat(join(root, entry.name))).size} bytes`);
}

// Every path below demo, sorted.
console.log('all:', (await readdir(root, {recursive: true})).sort().join(', '));

try {
  await rmdir(join(root, 'notes'));
} catch (error) {
  console.log('rmdir notes:', error.code);
}
await rm(join(root, 'notes'), {recursive: true});
console.log('notes after rm:', await stat(join(root, 'notes'), {throwIfNoEntry: false}));
await rm(root, {recursive: true});

Ausführen mit

node main.js

Ausgabe

first folder created: demo
  README.md 7 bytes
  notes/
  todo.txt 9 bytes
all: README.md, notes, notes/2026, notes/2026/jan.md, todo.txt
rmdir notes: ENOTEMPTY
notes after rm: undefined
  • mkdir mit recursive hat den ersten Ordner geliefert, den es anlegen musste, demo, nicht den tiefsten.
  • README.md steht vor notes, weil Großbuchstaben vor Kleinbuchstaben sortiert werden.
  • Die rekursive Liste liefert Pfade relativ zu demo, die Ordner selbst eingeschlossen.
  • rmdir hat einen Ordner mit Einträgen abgelehnt; rm mit recursive hat ihn mit allem darin entfernt.

Übungen

Übung 1 von 2

Eine Ordnerliste zusammenfassen

readdir und stat liefern Ihnen Datensätze wie {name: "img", size: 4096, isDirectory: true}. Schreiben Sie summarize(entries), das die Zeilen einer Liste zurückgibt: zuerst Ordner, als img/, dann Dateien, als notes.txt (120 bytes), jede Gruppe nach Namen sortiert, wie sort() Strings sortiert, und zuletzt eine Zeile wie 1 folders, 2 files, 150 bytes. Zählen Sie nur die Bytes der Dateien: Die Größe eines Ordners ist nicht sein Inhalt. Dieser Teil ist rein, er läuft also auch im Browser.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser den JavaScript-Runner herunter (bis zu 0.1 MB) und speichert ihn im Cache. Ihr Code läuft in der eigenen Engine Ihres Browsers und bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Teilen Sie die Liste mit filter in zwei: entries.filter((e) => e.isDirectory) und den Rest.

  2. Hinweis 2

    Sortieren Sie jeden Teil mit einer Vergleichsfunktion: (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0).

  3. Hinweis 3

    Addieren Sie nur die Größen der Dateien, mit reduce((sum, f) => sum + f.size, 0).

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

// entries: [{name, size, isDirectory}], as readdir and stat describe them.
// Folders first, then files, each sorted by name; a summary line last.
export function summarize(entries) {
  const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
  const folders = entries.filter((e) => e.isDirectory).sort(byName);
  const files = entries.filter((e) => !e.isDirectory).sort(byName);
  const bytes = files.reduce((sum, f) => sum + f.size, 0);
  return [
    ...folders.map((f) => f.name + '/'),
    ...files.map((f) => f.name + ' (' + f.size + ' bytes)'),
    folders.length + ' folders, ' + files.length + ' files, ' + bytes + ' bytes'
  ];
}

const entries = [
  {name: 'notes.txt', size: 120, isDirectory: false},
  {name: 'img', size: 4096, isDirectory: true},
  {name: 'README.md', size: 30, isDirectory: false}
];
console.log(summarize(entries).join('\n'));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

// entries: [{name, size, isDirectory}], as readdir and stat describe them.
// Folders first, then files, each sorted by name; a summary line last.
export function summarize(entries) {
  return entries.map((e) => e.name);
}

const entries = [
  {name: 'notes.txt', size: 120, isDirectory: false},
  {name: 'img', size: 4096, isDirectory: true},
  {name: 'README.md', size: 30, isDirectory: false}
];
console.log(summarize(entries).join('\n'));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {summarize} from './main.js';

const entries = [
  {name: 'notes.txt', size: 120, isDirectory: false},
  {name: 'img', size: 4096, isDirectory: true},
  {name: 'README.md', size: 30, isDirectory: false}
];

test('zuerst Ordner, dann Dateien, jeweils nach Namen sortiert', () => {
  const got = summarize(entries).slice(0, 3);
  assert.deepEqual(got, ['img/', 'README.md (30 bytes)', 'notes.txt (120 bytes)'], `die ersten Zeilen waren ${JSON.stringify(got)}`);
});

test('die letzte Zeile zählt die Ordner, die Dateien und deren Bytes', () => {
  const got = summarize(entries).at(-1);
  assert.equal(got, '1 folders, 2 files, 150 bytes', `die letzte Zeile war ${JSON.stringify(got)}`);
});

test('auch zwei Ordner werden sortiert', () => {
  const got = summarize([{name: 'src', size: 4096, isDirectory: true}, {name: 'docs', size: 4096, isDirectory: true}]);
  assert.deepEqual(got, ['docs/', 'src/', '2 folders, 0 files, 0 bytes'], `summarize lieferte ${JSON.stringify(got)}`);
});

test('ein leerer Ordner ergibt nur die Zusammenfassungszeile', () => {
  const got = summarize([]);
  assert.deepEqual(got, ['0 folders, 0 files, 0 bytes'], `summarize([]) lieferte ${JSON.stringify(got)}`);
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Übung 2 von 2

Einen echten Ordner beschreiben

Schreiben Sie describe(dir), das den Ordner dir liest und pro Eintrag einen Datensatz {name, size, isDirectory} liefert, nach Namen sortiert. Nehmen Sie readdir mit withFileTypes für den Typ und stat mit join(dir, entry.name) für die Größe. Ein fehlender Ordner (ENOENT) ergibt eine leere Liste; jeder andere Fehler, etwa ENOTDIR für eine Datei, muss ablehnen. Der Code darunter baut einen Beispielordner und gibt ihn aus. Starten Sie node main.js und prüfen Sie mit node --test.

Diese Übung braucht Node.js auf Ihrem Computer (die Browser-Version kann sie nicht ausführen). Dateien und Befehle stehen unten.

Hinweise
  1. Hinweis 1

    readdir(dir, {withFileTypes: true}) liefert Dirent-Objekte: entry.name und entry.isDirectory().

  2. Hinweis 2

    Geben Sie im catch-Block nur dann [] zurück, wenn error.code ENOENT ist, und werfen Sie den Fehler sonst erneut.

  3. Hinweis 3

    stat(join(dir, entry.name)).size ist die Größe; sortieren Sie die Datensätze mit einer Vergleichsfunktion über name.

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

import {mkdir, readdir, rm, stat, writeFile} from 'node:fs/promises';
import {join} from 'node:path';

// Describes the entries of dir as {name, size, isDirectory}, sorted by name.
// A missing dir gives an empty list; every other error is thrown again.
export async function describe(dir) {
  let entries;
  try {
    entries = await readdir(dir, {withFileTypes: true});
  } catch (error) {
    if (error.code === 'ENOENT') return [];
    throw error;
  }
  const records = [];
  for (const entry of entries) {
    const info = await stat(join(dir, entry.name));
    records.push({name: entry.name, size: info.size, isDirectory: entry.isDirectory()});
  }
  return records.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
}

// A sample folder next to main.js, made afresh on every run.
const sample = join(import.meta.dirname, 'sample');
await rm(sample, {recursive: true, force: true});
await mkdir(join(sample, 'img'), {recursive: true});
await writeFile(join(sample, 'todo.txt'), 'buy milk\n');
await writeFile(join(sample, 'README.md'), '# Sample\n');
for (const r of await describe(sample)) console.log(r.isDirectory ? r.name + '/' : r.name + ' ' + r.size + ' bytes');
console.log('missing:', await describe(join(import.meta.dirname, 'missing')));
Auf dem eigenen Computer ausführen

Installieren Sie Node.js 24 LTS oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.js

import {mkdir, readdir, rm, stat, writeFile} from 'node:fs/promises';
import {join} from 'node:path';

// Describes the entries of dir as {name, size, isDirectory}, sorted by name.
// A missing dir gives an empty list; every other error is thrown again.
export async function describe(dir) {
  let names;
  try {
    names = await readdir(dir);
  } catch {
    return [];
  }
  return names.map((name) => ({name, size: 0, isDirectory: false}));
}

// A sample folder next to main.js, made afresh on every run.
const sample = join(import.meta.dirname, 'sample');
await rm(sample, {recursive: true, force: true});
await mkdir(join(sample, 'img'), {recursive: true});
await writeFile(join(sample, 'todo.txt'), 'buy milk\n');
await writeFile(join(sample, 'README.md'), '# Sample\n');
for (const r of await describe(sample)) console.log(r.isDirectory ? r.name + '/' : r.name + ' ' + r.size + ' bytes');
console.log('missing:', await describe(join(import.meta.dirname, 'missing')));

main.test.js

import {test} from 'node:test';
import assert from 'node:assert/strict';
import {runMain} from './learnrun.js';
import {describe} from './main.js';

test('gibt den Beispielordner nach Namen sortiert aus, mit den Dateigrößen', async () => {
  const got = (await runMain()).trimEnd().split('\n').slice(0, 3);
  assert.deepEqual(got, ['README.md 9 bytes', 'img/', 'todo.txt 9 bytes'], `das Programm gab ${JSON.stringify(got)} aus`);
});

test('ein fehlender Ordner ergibt eine leere Liste', async () => {
  const last = (await runMain()).trimEnd().split('\n').at(-1);
  assert.equal(last, 'missing: []', `die letzte Zeile war ${JSON.stringify(last)}`);
});

test('eine Datei statt eines Ordners lehnt mit ENOTDIR ab', async () => {
  await assert.rejects(describe(new URL('./main.js', import.meta.url)), {code: 'ENOTDIR'}, 'describe auf main.js sollte mit dem Code ENOTDIR ablehnen');
});

package.json

{
  "type": "module"
}

package.json sagt Node.js, dass die .js-Dateien Module sind; lassen Sie die Datei im Ordner.

Programm ausführen:

node main.js

Prüfungen ausführen (learnrun.js muss im selben Ordner liegen):

node --test
learnrun.js herunterladen

Häufige Fehler

Einen Namen fragen, ob er ein Ordner ist

import {readdir} from 'node:fs/promises';

for (const entry of await readdir('.')) {
  if (entry.isDirectory()) console.log(entry + '/');
}

Was Node.js ausgibt

TypeError: entry.isDirectory is not a function

Warum, und die Lösung

Ohne Optionen liefert readdir Strings, und ein String hat kein isDirectory. Fordern Sie Dirent-Objekte an: readdir(".", {withFileTypes: true}), und nehmen Sie dann entry.name für den Namen und entry.isDirectory() für den Typ.

Einen vollen Ordner mit rmdir entfernen

import {mkdir, rmdir, writeFile} from 'node:fs/promises';

await mkdir('data', {recursive: true});
await writeFile('data/notes.json', '[]\n');
await rmdir('data');

Was Node.js ausgibt

Error: ENOTEMPTY: directory not empty, rmdir 'data'

Warum, und die Lösung

rmdir entfernt nur einen leeren Ordner. Um einen Ordner mit allem darin zu entfernen, nehmen Sie rm("data", {recursive: true}); ergänzen Sie force: true, wenn auch ein fehlender Ordner kein Fehler sein soll. Prüfen Sie, dass der Pfad der gemeinte ist: Rekursives Entfernen fragt nicht nach.

rm auf einen Ordner ohne recursive

import {mkdir, rm, writeFile} from 'node:fs/promises';

await mkdir('data', {recursive: true});
await writeFile('data/notes.json', '[]\n');
await rm('data');

Was Node.js ausgibt

SystemError [ERR_FS_EISDIR]: Path is a directory: rm returned EISDIR (is a directory) data

Warum, und die Lösung

Ohne Optionen entfernt rm nur Dateien, und ein Ordner wird abgelehnt. Beachten Sie den Code: error.code ist hier ERR_FS_EISDIR, nicht EISDIR. Übergeben Sie {recursive: true}, um den Ordner mit Inhalt zu entfernen.

JavaScript im Browser: die eigene Engine Ihres Browsers in einem abgeschotteten Worker. Syntaxfehler findet acorn 8.18.0, MIT. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Ordner anlegen, auflisten, entfernen

mkdir(dir, {recursive: true}) legt jeden fehlenden Ordner auf dem Weg an und liefert den ersten, den es angelegt hat, oder undefined, wenn alle schon existierten. readdir(dir) liefert die Namen darin, ohne . und .. und ohne den Ordnerteil. {withFileTypes: true} liefert Dirent-Objekte mit name, parentPath, isFile() und isDirectory(); {recursive: true}, vorhanden seit v20.1.0, nimmt alles darunter dazu, als Pfade wie notes/jan.md. Die Doku verspricht keine Reihenfolge, also sortieren Sie vor der Ausgabe. rmdir entfernt nur einen leeren Ordner. rm(dir, {recursive: true}) entfernt einen Ordner mit Inhalt, und force: true ignoriert einen fehlenden Pfad, wie rm -rf. rename verschiebt einen Ordner, cp(src, dest, {recursive: true}) kopiert ihn.

stat: Größe, Typ und Zeit

stat(path) liefert ein Stats-Objekt. size ist die Länge in Bytes, nicht in Zeichen: Grüße mit Zeilenumbruch sind 8 Bytes. isFile() und isDirectory() sagen, was der Pfad ist. mtime ist ein Date der letzten Änderung am Inhalt, mtimeMs dasselbe in Millisekunden, praktisch, um die neuesten zuerst zu sortieren: (a, b) => b.mtimeMs - a.mtimeMs. Die Größe eines Ordners ist nicht die Größe seines Inhalts. stat folgt einem symbolischen Link zu seinem Ziel; lstat beschreibt den Link selbst. Ein Dirent kennt Datei oder Ordner schon, stat brauchen Sie also nur für Größe und Zeiten. Rufen Sie es mit join(dir, entry.name) auf: Der Name allein wird gegen den aktuellen Ordner aufgelöst.

Einfach versuchen, und error.code lesen

Es ist verlockend, zuerst zu fragen, ob ein Ordner existiert. Die fs-Doku rät davon ab, access vor dem Öffnen, Lesen oder Schreiben aufzurufen, und sagt dasselbe über stat: Ein anderer Prozess kann die Datei zwischen den beiden Aufrufen ändern. Führen Sie stattdessen die Operation aus und behandeln Sie den Fehler. Die Codes bei Ordnern: ENOENT, ein Teil des Pfads existiert nicht; ENOTDIR, ein Teil des Pfads ist eine Datei, wo ein Ordner erwartet wurde, wie bei readdir("notes.txt"); ENOTEMPTY, rmdir auf einen Ordner, der noch Einträge hat; EEXIST, mkdir ohne recursive auf einen vorhandenen Ordner. Seit v24.15.0 liefert stat(path, {throwIfNoEntry: false}) für einen fehlenden Pfad undefined, statt abzulehnen.

Quellen

Zuletzt geprüft am 4. Oktober 2026