Appearance
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
| Aspekt | Synchron (*Sync) | Asynchron (Callback/Promise) |
|---|---|---|
| Blockierung | Blockiert Event Loop | Blockiert nicht |
| Performance | Langsam bei großen Dateien | Hochleistungsfähig |
| Fehlerbehandlung | try/catch | Callback-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.js9.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
