Skip to content

Kapitel 9: Dateisystem fortgeschritten

🎯 Lernziele

In diesem Kapitel lernen Sie:

  • ✅ Synchron vs. asynchron Dateioperationen
  • ✅ Ordneroperationen (erstellen, löschen, traversieren)
  • ✅ Dateien batch-verarbeiten (Massenoperationen)
  • path-Modul fortgeschritten verwenden
  • ✅ Praxis: Dateikopie, Ordner-Traversierung, Log-Schreibung

9.1 Synchron vs. Asynchron

📖 Unterschiede

AspektSynchron (*Sync)Asynchron (Callback/Promise)
BlockierungBlockiert Event LoopBlockiert nicht
PerformanceLangsam bei großen DateienHochleistungsfähig
Fehlerbehandlungtry/catchCallback-Error oder .catch()
Empfehlung❌ Nicht für Produktion✅ Immer verwenden!

💡 Code-Beispiel

javascript
const fs = require('fs');

// ❌ Synchron (Blockierend - Schlecht!)
console.time('Sync');
const datenSync = fs.readFileSync('große-datei.txt', 'utf8');
console.timeEnd('Sync');  // → 500ms (Event Loop blockiert!)

// ✅ Asynchron (Nicht-blockierend - Gut!)
console.time('Async');
fs.readFile('große-datei.txt', 'utf8', (err, daten) => {
  console.timeEnd('Async');  // → 500ms (Aber nicht blockiert!)
  // Andere Operationen können parallel laufen
});
console.log('Dies wird sofort ausgegeben!');  // ⭐ SOFORT!

🎨 Visuelle Erklärung

Synchron (Blockierend):
┌─────────────────────────────────┐
│  Lese Datei... (500ms)       │ ← Blockiert!
│  [Warten...]                  │
│  Weiter mit anderem Code      │ ← Muss warten
└─────────────────────────────────┘

Asynchron (Nicht-blockierend):
┌─────────────────────────────────┐
│  Starte Dateilesen (Hintergrund) │
│  Weiter mit anderem Code      │ ← ⭐ Sofort!
│  [... 500ms später ...]      │
│  Callback wird ausgeführt    │
└─────────────────────────────────┘

9.2 Ordneroperationen

📁 Ordner erstellen

javascript
const fs = require('fs').promises;  // Promise-API verwenden

// Einzelnen Ordner erstellen
async function ordnerErstellen() {
  try {
    await fs.mkdir('mein-ordner');
    console.log('✅ Ordner erstellt!');
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

// Rekursiv (Unterordner automatisch erstellen)
async function ordnerRekursiv() {
  try {
    await fs.mkdir('pfad/zu/meinem/ordner', { recursive: true });
    console.log('✅ Ordner-Struktur erstellt!');
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

ordnerRekursiv();

🗑️ Ordner löschen

javascript
const fs = require('fs').promises;

// ⚠️ Achtung: Ordner muss LEER sein!
async function ordnerLoeschen() {
  try {
    await fs.rmdir('mein-ordner');
    console.log('✅ Ordner gelöscht!');
  } catch (err) {
    if (err.code === 'ENOTEMPTY') {
      console.error('❌ Ordner ist nicht leer!');
    } else {
      console.error('❌ Fehler:', err.message);
    }
  }
}

// ✅ Rekursiv löschen (Gefährlich! Alle Dateien werden gelöscht!)
async function ordnerRekursivLoeschen() {
  try {
    await fs.rm('mein-ordner', { recursive: true, force: true });
    console.log('✅ Ordner (inkl. Inhalt) gelöscht!');
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

🔍 Ordner traversieren (Durchlaufen)

javascript
const fs = require('fs').promises;
const path = require('path');

// Alle Dateien in einem Ordner auflisten
async function dateienAuflisten() {
  try {
    const dateien = await fs.readdir('mein-ordner');
    console.log('📂 Dateien:');
    dateien.forEach(datei => {
      console.log(`  - ${datei}`);
    });
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

// Mit Datei-Informationen (Stat)
async function dateienMitInfo() {
  try {
    const dateien = await fs.readdir('mein-ordner');
    
    for (const datei of dateien) {
      const pfad = path.join('mein-ordner', datei);
      const stat = await fs.stat(pfad);
      
      if (stat.isDirectory()) {
        console.log(`📁 ${datei} (Ordner)`);
      } else if (stat.isFile()) {
        console.log(`📄 ${datei} (${stat.size} Bytes)`);
      }
    }
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

🔄 Rekursive Ordner-Traversierung

javascript
const fs = require('fs').promises;
const path = require('path');

// Alle Dateien rekursiv durchsuchen
async function ordnerTraversieren(ordnerPfad) {
  try {
    const elemente = await fs.readdir(ordnerPfad);
    
    for (const element of elemente) {
      const elementPfad = path.join(ordnerPfad, element);
      const stat = await fs.stat(elementPfad);
      
      if (stat.isDirectory()) {
        console.log(`📁 Ordner: ${elementPfad}`);
        // Rekursion! (Unterordner durchsuchen)
        await ordnerTraversieren(elementPfad);
      } else {
        console.log(`📄 Datei: ${elementPfad}`);
      }
    }
  } catch (err) {
    console.error(`❌ Fehler in ${ordnerPfad}:`, err.message);
  }
}

// Verwendung
ordnerTraversieren('./mein-projekt');

Ausgabe-Beispiel:

📁 Ordner: ./mein-projekt
📄 Datei: ./mein-projekt/index.js
📄 Datei: ./mein-projekt/package.json
📁 Ordner: ./mein-projekt/src
📄 Datei: ./mein-projekt/src/app.js
📄 Datei: ./mein-projekt/src/utils.js

9.3 Dateien batch-verarbeiten

📦 Mehrere Dateien gleichzeitig lesen

javascript
const fs = require('fs').promises;

// ❌ Nacheinander lesen (Langsam!)
async function nacheinander() {
  console.time('Nacheinander');
  
  const daten1 = await fs.readFile('datei1.txt', 'utf8');
  const daten2 = await fs.readFile('datei2.txt', 'utf8');
  const daten3 = await fs.readFile('datei3.txt', 'utf8');
  
  console.timeEnd('Nacheinander');  // → 300ms (100ms * 3)
  return [daten1, daten2, daten3];
}

// ✅ Parallel lesen (Schnell!) ⭐
async function parallel() {
  console.time('Parallel');
  
  const [daten1, daten2, daten3] = await Promise.all([
    fs.readFile('datei1.txt', 'utf8'),
    fs.readFile('datei2.txt', 'utf8'),
    fs.readFile('datei3.txt', 'utf8')
  ]);
  
  console.timeEnd('Parallel');  // → 100ms (Alle gleichzeitig!)
  return [daten1, daten2, daten3];
}

🔄 Alle .txt-Dateien in einem Ordner verarbeiten

javascript
const fs = require('fs').promises;
const path = require('path');

async function alleTxtDateienVerarbeiten(ordner) {
  try {
    // Alle Dateien auflisten
    const dateien = await fs.readdir(ordner);
    
    // Nur .txt Dateien filtern
    const txtDateien = dateien.filter(datei => path.extname(datei) === '.txt');
    
    // Alle Dateien parallel lesen
    const leseVersprechen = txtDateien.map(datei => {
      const pfad = path.join(ordner, datei);
      return fs.readFile(pfad, 'utf8');
    });
    
    const inhaltArray = await Promise.all(leseVersprechen);
    
    // Ergebnisse verarbeiten
    inhaltArray.forEach((inhalt, index) => {
      console.log(`📄 ${txtDateien[index]}:`);
      console.log(`   ${inhalt.substring(0, 50)}...`);
    });
    
    return inhaltArray;
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

// Verwendung
alleTxtDateienVerarbeiten('./daten');

📋 Dateien kopieren (Copy)

javascript
const fs = require('fs').promises;

// Einfache Dateikopie
async function dateiKopieren(quelle, ziel) {
  try {
    await fs.copyFile(quelle, ziel);
    console.log(`✅ Datei kopiert: ${quelle} → ${ziel}`);
  } catch (err) {
    console.error('❌ Fehler beim Kopieren:', err.message);
  }
}

// Mehrere Dateien kopieren
async function dateienKopieren(quelldateien, zielOrdner) {
  try {
    await Promise.all(
      quelldateien.map(datei => {
        const zielPfad = path.join(zielOrdner, path.basename(datei));
        return fs.copyFile(datei, zielPfad);
      })
    );
    console.log(`✅ ${quelldateien.length} Dateien kopiert!`);
  } catch (err) {
    console.error('❌ Fehler:', err.message);
  }
}

// Verwendung
dateiKopieren('./quelle.txt', './backup/quelle-kopie.txt');
dateienKopieren(['./a.txt', './b.txt', './c.txt'], './backup');

9.4 Path-Modul fortgeschritten

🗺️ Pfad-Normalisierung

javascript
const path = require('path');

// Pfade bereinigen (.. und . auflösen)
console.log(path.normalize('./daten/../bilder/./logo.png'));
// → 'bilder/logo.png' (Windows: 'bilder\\logo.png')

// Absoluten Pfad erhalten
console.log(path.resolve('daten/datei.txt'));
// → 'C:\\Users\\Max\\Projekte\\mein-projekt\\daten\\datei.txt'

// Relativen Pfad zwischen zwei Pfaden berechnen
console.log(path.relative('/a/b/c', '/a/d'));
// → '..\\..\\d' (Windows) oder '../../d' (macOS/Linux)

🔒 Sicherheitscheck: Path Traversal verhindern

javascript
const path = require('path');
const fs = require('fs');

// ❌ UNSICHER! Path Traversal Angriff möglich!
function unsicher(dateiname) {
  const pfad = './uploads/' + dateiname;  // Gefährlich!
  return fs.readFileSync(pfad, 'utf8');
}

// Angreifer könnte eingeben: ../../../etc/passwd

// ✅ SICHER! Path Traversal verhindern
function sicher(dateiname) {
  const basisPfad = path.resolve('./uploads');
  const dateiPfad = path.resolve(path.join('./uploads', dateiname));
  
  // Überprüfen, ob der Dateipfad im erlaubten Ordner liegt
  if (!dateiPfad.startsWith(basisPfad)) {
    throw new Error('Zugriff verweigert!');
  }
  
  return fs.readFileSync(dateiPfad, 'utf8');
}

// Oder einfacher:
function sicher2(dateiname) {
  const dateiPfad = path.join('./uploads', path.basename(dateiname));
  return fs.readFileSync(dateiPfad, 'utf8');
}

🧩 Plattformübergreifende Pfade

javascript
const path = require('path');

// ❌ Schlecht (Funktioniert nur auf einem Betriebssystem!)
const pfadWindows = 'C:\\Projekte\\datei.txt';
const pfadLinux = '/home/user/datei.txt';

// ✅ Gut (Funktioniert überall!)
const pfad = path.join('Projekte', 'unterordner', 'datei.txt');
// Windows: 'Projekte\\unterordner\\datei.txt'
// macOS/Linux: 'Projekte/unterordner/datei.txt'

// Trennzeichen ermitteln
console.log(path.sep);  // Windows: \  |  macOS/Linux: /

// Pfad-Delimiter (für PATH-Variable)
console.log(path.delimiter);  // Windows: ;  |  macOS/Linux: :

9.5 Praxisbeispiele

📝 Fallstudie 1: Log-Datei schreiben

javascript
const fs = require('fs').promises;
const path = require('path');

class Logger {
  constructor(logOrdner = './logs') {
    this.logOrdner = logOrdner;
    this.initialisieren();
  }
  
  // Log-Ordner erstellen (falls nicht vorhanden)
  async initialisieren() {
    try {
      await fs.mkdir(this.logOrdner, { recursive: true });
    } catch (err) {
      console.error('❌ Kann Log-Ordner nicht erstellen:', err.message);
    }
  }
  
  // Log-Nachricht schreiben
  async log(nachricht, level = 'INFO') {
    try {
      const zeitstempel = new Date().toISOString();
      const logZeile = `[${zeitstempel}] [${level}] ${nachricht}\n`;
      
      // Log-Datei für heute erstellen
      const datumsString = new Date().toISOString().split('T')[0];
      const logDateiPfad = path.join(this.logOrdner, `app-${datumsString}.log`);
      
      // An Datei anhängen (append)
      await fs.appendFile(logDateiPfad, logZeile, 'utf8');
      
    } catch (err) {
      console.error('❌ Fehler beim Schreiben des Logs:', err.message);
    }
  }
  
  // Hilfsmethoden
  async info(nachricht) {
    await this.log(nachricht, 'INFO');
  }
  
  async warn(nachricht) {
    await this.log(nachricht, 'WARN');
  }
  
  async error(nachricht) {
    await this.log(nachricht, 'ERROR');
  }
  
  // Alle Log-Dateien auflisten
  async logDateienAuflisten() {
    try {
      const dateien = await fs.readdir(this.logOrdner);
      const logDateien = dateien.filter(d => d.endsWith('.log'));
      
      console.log('📋 Verfügbare Log-Dateien:');
      logDateien.forEach(datei => {
        console.log(`  - ${datei}`);
      });
      
      return logDateien;
    } catch (err) {
      console.error('❌ Fehler:', err.message);
      return [];
    }
  }
}

// Verwendung
const logger = new Logger('./logs');

async function main() {
  await logger.info('Anwendung gestartet');
  await logger.info('Verbindung zur Datenbank hergestellt');
  await logger.warn('Langsame Antwortzeit: 2000ms');
  await logger.error('Datenbankverbindung fehlgeschlagen');
  
  await logger.logDateienAuflisten();
}

main();

Ausgabe in logs/app-2024-01-15.log:

[2024-01-15T10:30:00.000Z] [INFO] Anwendung gestartet
[2024-01-15T10:30:01.000Z] [INFO] Verbindung zur Datenbank hergestellt
[2024-01-15T10:30:02.000Z] [WARN] Langsame Antwortzeit: 2000ms
[2024-01-15T10:30:03.000Z] [ERROR] Datenbankverbindung fehlgeschlagen

📂 Fallstudie 2: Ordner-Traversierung & Dateisuche

javascript
const fs = require('fs').promises;
const path = require('path');

// Alle Dateien mit einer bestimmten Endung suchen
async function dateienSuchen(ordner, endung) {
  const gefundeneDateien = [];
  
  async function traversieren(aktuellerOrdner) {
    try {
      const elemente = await fs.readdir(aktuellerOrdner);
      
      for (const element of elemente) {
        const elementPfad = path.join(aktuellerOrdner, element);
        const stat = await fs.stat(elementPfad);
        
        if (stat.isDirectory()) {
          // Rekursion: Unterordner durchsuchen
          await traversieren(elementPfad);
        } else if (element.endsWith(endung)) {
          // Datei mit passender Endung gefunden!
          gefundeneDateien.push(elementPfad);
        }
      }
    } catch (err) {
      console.error(`❌ Fehler in ${aktuellerOrdner}:`, err.message);
    }
  }
  
  await traversieren(ordner);
  return gefundeneDateien;
}

// Verwendung: Alle .js Dateien suchen
dateienSuchen('./src', '.js')
  .then(jsDateien => {
    console.log('📄 Gefundene JavaScript-Dateien:');
    jsDateien.forEach(datei => {
      console.log(`  - ${datei}`);
    });
  });

// Ergebnis:
// 📄 Gefundene JavaScript-Dateien:
//   - src/app.js
//   - src/utils/helper.js
//   - src/components/header.js

📦 Fallstudie 3: Backup-Skript (Dateien kopieren)

javascript
const fs = require('fs').promises;
const path = require('path');

async function backupErstellen(quelleOrdner, backupOrdner) {
  try {
    // Backup-Ordner erstellen
    await fs.mkdir(backupOrdner, { recursive: true });
    
    // Alle Dateien im Quellordner auflisten
    const elemente = await fs.readdir(quelleOrdner);
    
    console.log(`📦 Starte Backup von ${quelleOrdner}...`);
    
    let kopierteDateien = 0;
    
    for (const element of elemente) {
      const quellePfad = path.join(quelleOrdner, element);
      const zielPfad = path.join(backupOrdner, element);
      const stat = await fs.stat(quellePfad);
      
      if (stat.isDirectory()) {
        // Rekursion: Unterordner backupen
        await backupErstellen(quellePfad, zielPfad);
      } else {
        // Datei kopieren
        await fs.copyFile(quellePfad, zielPfad);
        kopierteDateien++;
        console.log(`  ✅ ${element} kopiert`);
      }
    }
    
    console.log(`✅ Backup abgeschlossen! (${kopierteDateien} Dateien)`);
    return kopierteDateien;
    
  } catch (err) {
    console.error('❌ Fehler beim Backup:', err.message);
    return 0;
  }
}

// Verwendung
backupErstellen('./wichtig', './backup/wichtig-' + Date.now());

📝 Zusammenfassung

In diesem Kapitel haben Sie gelernt:

  • ✅ Synchron vs. Asynchron (Immer asynchron verwenden!)
  • ✅ Ordner erstellen (mkdir), löschen (rmdir/rm)
  • ✅ Ordner traversieren (readdir, rekursiv)
  • ✅ Dateien batch-verarbeiten (Promise.all)
  • ✅ Dateien kopieren (copyFile)
  • ✅ Pfade sicher verarbeiten (path-Modul)
  • ✅ Praxis: Logger, Dateisuche, Backup-Skript

🎯 Nächste Schritte

Im nächsten Kapitel werden wir:

  • Express.js Framework kennenlernen
  • Express installieren und konfigurieren
  • Express-Server erstellen
  • Routing mit Express implementieren
  • Middleware-Konzept verstehen

📚 Weiterführende Ressourcen


🎉 Kapitel 9 abgeschlossen! Weiter zu Kapitel 10: Express Framework Einführung

Frei für alle Anfänger