Shell-Skripting mit Node.js
Sie können die Offline-Version dieses Buches (HTML, PDF, EPUB, MOBI) kaufen und damit die kostenlose Online-Version unterstützen.
(Werbung, bitte nicht blockieren.)

8 Arbeiten mit dem Dateisystem unter Node.js



Dieses Kapitel enthält

Da der Fokus dieses Buches auf Shell-Skripting liegt, arbeiten wir nur mit Textdaten.

8.1 Konzepte, Muster und Konventionen der Node.js-Dateisystem-APIs

8.1.1 Wege des Zugriffs auf Dateien

  1. Wir können den gesamten Inhalt einer Datei über einen String lesen oder schreiben.
  2. Wir können einen Stream zum Lesen oder einen Stream zum Schreiben öffnen und eine Datei in kleineren Teilen verarbeiten, jeweils einzeln. Streams erlauben nur sequenziellen Zugriff.
  3. Wir können Dateideskriptoren oder FileHandles verwenden und sowohl sequenziellen als auch zufälligen Zugriff über eine API erhalten, die Streams lose ähnelt.
    • Dateideskriptoren sind Ganzzahlen, die Dateien repräsentieren. Sie werden über die folgenden Funktionen verwaltet (nur die synchronen Namen sind aufgeführt, es gibt auch Callback-basierte Versionen – fs.open() usw.)
      • fs.openSync(path, flags?, mode?) öffnet einen neuen Dateideskriptor für eine Datei unter einem bestimmten Pfad und gibt ihn zurück.
      • fs.closeSync(fd) schließt einen Dateideskriptor.
      • fs.fchmodSync(fd, mode)
      • fs.fchownSync(fd, uid, gid)
      • fs.fdatasyncSync(fd)
      • fs.fstatSync(fd, options?)
      • fs.fsyncSync(fd)
      • fs.ftruncateSync(fd, len?)
      • fs.futimesSync(fd, atime, mtime)
    • Nur die synchrone API und die Callback-basierte API verwenden Dateideskriptoren. Die Promise-basierte API hat eine bessere Abstraktion, die Klasse FileHandle, die auf Dateideskriptoren basiert. Instanzen werden über fsPromises.open() erstellt. Verschiedene Operationen werden über Methoden bereitgestellt (nicht über Funktionen)
      • fileHandle.close()
      • fileHandle.chmod(mode)
      • fileHandle.chown(uid, gid)
      • Usw.

Beachten Sie, dass wir in diesem Kapitel (3) nicht verwenden – (1) und (2) reichen für unsere Zwecke aus.

8.1.2 Präfixe von Funktionsnamen

Funktionen, deren Namen mit einem „l“ beginnen, arbeiten normalerweise mit symbolischen Links

8.1.2.2 Präfix „f“: Dateideskriptoren

Funktionen, deren Namen mit einem „f“ beginnen, verwalten normalerweise Dateideskriptoren

8.1.3 Wichtige Klassen

Mehrere Klassen spielen in den Dateisystem-APIs von Node eine wichtige Rolle.

8.1.3.1 URLs: eine Alternative zu Dateisystempfaden in Strings

Immer wenn eine Node.js-Funktion einen Dateisystempfad als String akzeptiert (Zeile A), akzeptiert sie normalerweise auch eine Instanz von URL (Zeile B)

assert.equal(
  fs.readFileSync(
    '/tmp/text-file.txt', {encoding: 'utf-8'}), // (A)
  'Text content'
);
assert.equal(
  fs.readFileSync(
    new URL('file:///tmp/text-file.txt'), {encoding: 'utf-8'}), // (B)
  'Text content'
);

Die manuelle Konvertierung zwischen Pfaden und file:-URLs scheint einfach zu sein, hat aber überraschend viele Fallstricke: Prozentkodierung oder -dekodierung, Windows-Laufwerksbuchstaben usw. Stattdessen ist es besser, die folgenden beiden Funktionen zu verwenden

Wir verwenden in diesem Kapitel keine Datei-URLs. Anwendungsfälle dafür werden in §7.11.1 „Klasse URL beschrieben.

8.1.3.2 Buffers

Die Klasse Buffer repräsentiert Bytefolgen fester Länge in Node.js. Sie ist eine Unterklasse von Uint8Array (einem TypedArray). Buffer werden hauptsächlich bei der Arbeit mit Binärdateien verwendet und sind daher für dieses Buch von geringerem Interesse.

Immer wenn Node.js einen Buffer akzeptiert, akzeptiert es auch ein Uint8Array. Da Uint8Arrays plattformübergreifend sind und Buffer nicht, ist ersteres vorzuziehen.

Buffer können eine Sache, die Uint8Arrays nicht können: Text in verschiedenen Kodierungen kodieren und dekodieren. Wenn wir UTF-8 in Uint8Arrays kodieren oder dekodieren müssen, können wir die Klasse TextEncoder oder die Klasse TextDecoder verwenden. Diese Klassen sind auf den meisten JavaScript-Plattformen verfügbar

> new TextEncoder().encode('café')
Uint8Array.of(99, 97, 102, 195, 169)
> new TextDecoder().decode(Uint8Array.of(99, 97, 102, 195, 169))
'café'
8.1.3.3 Node.js-Streams

Einige Funktionen akzeptieren oder geben native Node.js-Streams zurück

Anstelle von nativen Streams können wir nun plattformübergreifende Web-Streams auf Node.js verwenden. Wie das geht, wird in §10 „Web-Streams unter Node.js verwenden“ erklärt.

8.2 Lesen und Schreiben von Dateien

8.2.1 Eine Datei synchron in einen einzigen String lesen (optional: Aufteilen in Zeilen)

fs.readFileSync(filePath, options?) liest die Datei unter filePath in einen einzigen String

assert.equal(
  fs.readFileSync('text-file.txt', {encoding: 'utf-8'}),
  'there\r\nare\nmultiple\nlines'
);

Vor- und Nachteile dieses Ansatzes (ggü. der Verwendung eines Streams)

Als Nächstes werden wir den eingelesenen String in Zeilen aufteilen.

8.2.1.1 Zeilen ohne Zeilenumbrüche aufteilen

Der folgende Code teilt einen String in Zeilen auf und entfernt dabei die Zeilenumbrüche. Er funktioniert sowohl mit Unix- als auch mit Windows-Zeilenumbrüchen

const RE_SPLIT_EOL = /\r?\n/;
function splitLines(str) {
  return str.split(RE_SPLIT_EOL);
}
assert.deepEqual(
  splitLines('there\r\nare\nmultiple\nlines'),
  ['there', 'are', 'multiple', 'lines']
);

„EOL“ steht für „End Of Line“. Wir akzeptieren sowohl Unix-Zeilenumbrüche ('\n') als auch Windows-Zeilenumbrüche ('\r\n', wie der erste im vorherigen Beispiel). Weitere Informationen finden Sie in §8.3 „Umgang mit Zeilenumbrüchen über Plattformen hinweg“.

8.2.1.2 Zeilen unter Beibehaltung der Zeilenumbrüche aufteilen

Der folgende Code teilt einen String in Zeilen auf und behält dabei die Zeilenumbrüche bei. Er funktioniert sowohl mit Unix- als auch mit Windows-Zeilenumbrüchen („EOL“ steht für „End Of Line“)

const RE_SPLIT_AFTER_EOL = /(?<=\r?\n)/; // (A)
function splitLinesWithEols(str) {
  return str.split(RE_SPLIT_AFTER_EOL);
}

assert.deepEqual(
  splitLinesWithEols('there\r\nare\nmultiple\nlines'),
  ['there\r\n', 'are\n', 'multiple\n', 'lines']
);
assert.deepEqual(
  splitLinesWithEols('first\n\nthird'),
  ['first\n', '\n', 'third']
);
assert.deepEqual(
  splitLinesWithEols('EOL at the end\n'),
  ['EOL at the end\n']
);
assert.deepEqual(
  splitLinesWithEols(''),
  ['']
);

Zeile A enthält einen regulären Ausdruck mit einer Lookbehind-Assertion. Er passt an Stellen, denen ein Treffer des Musters \r?\n vorausgeht, aber er erfasst nichts. Daher entfernt er nichts zwischen den String-Fragmenten, in die der Eingabestring aufgeteilt wird.

Auf Engines, die Lookbehind-Assertions nicht unterstützen (siehe diese Tabelle), können wir die folgende Lösung verwenden

function splitLinesWithEols(str) {
  if (str.length === 0) return [''];
  const lines = [];
  let prevEnd = 0;
  while (prevEnd < str.length) {
    // Searching for '\n' means we’ll also find '\r\n'
    const newlineIndex = str.indexOf('\n', prevEnd);
    // If there is a newline, it’s included in the line
    const end = newlineIndex < 0 ? str.length : newlineIndex+1;
    lines.push(str.slice(prevEnd, end));
    prevEnd = end;
  }
  return lines;
}

Diese Lösung ist einfach, aber umständlicher.

In beiden Versionen von splitLinesWithEols() akzeptieren wir wieder sowohl Unix-Zeilenumbrüche ('\n') als auch Windows-Zeilenumbrüche ('\r\n'). Weitere Informationen finden Sie in §8.3 „Umgang mit Zeilenumbrüchen über Plattformen hinweg“.

8.2.2 Eine Datei über einen Stream zeilenweise lesen

Wir können Textdateien auch über Streams lesen

import {Readable} from 'node:stream';

const nodeReadable = fs.createReadStream(
  'text-file.txt', {encoding: 'utf-8'});
const webReadableStream = Readable.toWeb(nodeReadable);
const lineStream = webReadableStream.pipeThrough(
  new ChunksToLinesStream());
for await (const line of lineStream) {
  console.log(line);
}

// Output:
// 'there\r\n'
// 'are\n'
// 'multiple\n'
// 'lines'

Wir haben die folgende externe Funktionalität verwendet

Web-Streams sind asynchron iterierbar, weshalb wir eine for-await-of-Schleife verwenden können, um über die Zeilen zu iterieren.

Wenn uns keine Textzeilen interessieren, benötigen wir ChunksToLinesStream nicht, können über webReadableStream iterieren und erhalten Chunks mit beliebiger Länge.

Weitere Informationen

Vor- und Nachteile dieses Ansatzes (ggü. dem Lesen eines einzelnen Strings)

8.2.3 Einen einzelnen String synchron in eine Datei schreiben

fs.writeFileSync(filePath, str, options?) schreibt str in eine Datei unter filePath. Wenn an diesem Pfad bereits eine Datei existiert, wird sie überschrieben.

Der folgende Code zeigt, wie diese Funktion verwendet wird

fs.writeFileSync(
  'new-file.txt',
  'First line\nSecond line\n',
  {encoding: 'utf-8'}
);

Informationen zu Zeilenumbrüchen finden Sie in §8.3 „Umgang mit Zeilenumbrüchen über Plattformen hinweg“.

Vor- und Nachteile (ggü. der Verwendung eines Streams)

8.2.4 Einen einzelnen String synchron an eine Datei anhängen

Der folgende Code hängt eine Textzeile an eine vorhandene Datei an

fs.appendFileSync(
  'existing-file.txt',
  'Appended line\n',
  {encoding: 'utf-8'}
);

Wir können auch fs.writeFileSync() für diese Aufgabe verwenden

fs.writeFileSync(
  'existing-file.txt',
  'Appended line\n',
  {encoding: 'utf-8', flag: 'a'}
);

Dieser Code ist fast identisch mit dem, den wir zum Überschreiben vorhandener Inhalte verwendet haben (siehe vorheriger Abschnitt für weitere Informationen). Der einzige Unterschied besteht darin, dass wir die Option .flag hinzugefügt haben: Der Wert 'a' bedeutet, dass wir Daten anhängen. Andere mögliche Werte (z. B. zum Auslösen eines Fehlers, wenn eine Datei noch nicht existiert) werden in der Node.js-Dokumentation erklärt.

Achtung: In einigen Funktionen heißt diese Option .flag, in anderen .flags.

8.2.5 Mehrere Strings über einen Stream in eine Datei schreiben

Der folgende Code verwendet einen Stream, um mehrere Strings in eine Datei zu schreiben

import {Writable} from 'node:stream';

const nodeWritable = fs.createWriteStream(
  'new-file.txt', {encoding: 'utf-8'});
const webWritableStream = Writable.toWeb(nodeWritable);

const writer = webWritableStream.getWriter();
try {
  await writer.write('First line\n');
  await writer.write('Second line\n');
  await writer.close();
} finally {
  writer.releaseLock()
}

Wir haben die folgenden Funktionen verwendet

Weitere Informationen

Vor- und Nachteile (ggü. dem Schreiben eines einzelnen Strings)

8.2.6 Mehrere Strings über einen Stream (asynchron) an eine Datei anhängen

Der folgende Code verwendet einen Stream, um Text an eine vorhandene Datei anzuhängen

import {Writable} from 'node:stream';

const nodeWritable = fs.createWriteStream(
  'existing-file.txt', {encoding: 'utf-8', flags: 'a'});
const webWritableStream = Writable.toWeb(nodeWritable);

const writer = webWritableStream.getWriter();
try {
  await writer.write('First appended line\n');
  await writer.write('Second appended line\n');
  await writer.close();
} finally {
  writer.releaseLock()
}

Dieser Code ist fast identisch mit dem, den wir zum Überschreiben vorhandener Inhalte verwendet haben (siehe vorheriger Abschnitt für weitere Informationen). Der einzige Unterschied besteht darin, dass wir die Option .flags hinzugefügt haben: Der Wert 'a' bedeutet, dass wir Daten anhängen. Andere mögliche Werte (z. B. zum Auslösen eines Fehlers, wenn eine Datei noch nicht existiert) werden in der Node.js-Dokumentation erklärt.

Achtung: In einigen Funktionen heißt diese Option .flag, in anderen .flags.

8.3 Umgang mit Zeilenumbrüchen über Plattformen hinweg

Leider haben nicht alle Plattformen die gleichen Zeilenumbruch-Zeichen, die das Zeilenende (EOL) markieren

Um EOL plattformübergreifend zu handhaben, können wir mehrere Strategien anwenden.

8.3.1 Zeilenumbrüche lesen

Beim Lesen von Text ist es am besten, beide EOLs zu erkennen.

Wie könnte das beim Aufteilen eines Textes in Zeilen aussehen? Wir können die EOLs (in beiden Formaten) am Ende einfügen. Dies ermöglicht es uns, so wenig wie möglich zu ändern, wenn wir diese Zeilen ändern und in eine Datei schreiben.

Bei der Verarbeitung von Zeilen mit EOLs ist es manchmal nützlich, sie zu entfernen – z. B. über die folgende Funktion

const RE_EOL_REMOVE = /\r?\n$/;
function removeEol(line) {
  const match = RE_EOL_REMOVE.exec(line);
  if (!match) return line;
  return line.slice(0, match.index);
}

assert.equal(
  removeEol('Windows EOL\r\n'),
  'Windows EOL'
);
assert.equal(
  removeEol('Unix EOL\n'),
  'Unix EOL'
);
assert.equal(
  removeEol('No EOL'),
  'No EOL'
);

8.3.2 Zeilenumbrüche schreiben

Beim Schreiben von Zeilenumbrüchen haben wir zwei Optionen

8.4 Verzeichnisse durchlaufen und erstellen

8.4.1 Ein Verzeichnis durchlaufen

Die folgende Funktion durchläuft ein Verzeichnis und listet alle seine Nachkommen auf (seine Kinder, die Kinder seiner Kinder usw.)

import * as path from 'node:path';

function* traverseDirectory(dirPath) {
  const dirEntries = fs.readdirSync(dirPath, {withFileTypes: true});
  // Sort the entries to keep things more deterministic
  dirEntries.sort(
    (a, b) => a.name.localeCompare(b.name, 'en')
  );
  for (const dirEntry of dirEntries) {
    const fileName = dirEntry.name;
    const pathName = path.join(dirPath, fileName);
    yield pathName;
    if (dirEntry.isDirectory()) {
      yield* traverseDirectory(pathName);
    }
  }
}

Wir haben diese Funktionalität verwendet

Der folgende Code zeigt traverseDirectory() in Aktion

for (const filePath of traverseDirectory('dir')) {
  console.log(filePath);
}

// Output:
// 'dir/dir-file.txt'
// 'dir/subdir'
// 'dir/subdir/subdir-file1.txt'
// 'dir/subdir/subdir-file2.csv'

8.4.2 Ein Verzeichnis erstellen (mkdir, mkdir -p)

Wir können die folgende Funktion zum Erstellen von Verzeichnissen verwenden

fs.mkdirSync(thePath, options?): undefined | string

options.recursive bestimmt, wie die Funktion das Verzeichnis unter thePath erstellt

Dies ist mkdirSync() in Aktion

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
  ]
);
fs.mkdirSync('dir/sub/subsub', {recursive: true});
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/sub',
    'dir/sub/subsub',
  ]
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.4.3 Sicherstellen, dass ein Elternverzeichnis existiert

Wenn wir bei Bedarf eine verschachtelte Dateistruktur einrichten möchten, können wir uns nicht immer sicher sein, dass die Elternverzeichnisse existieren, wenn wir eine neue Datei erstellen. Dann hilft die folgende Funktion

import * as path from 'node:path';

function ensureParentDirectory(filePath) {
  const parentDir = path.dirname(filePath);
  if (!fs.existsSync(parentDir)) {
    fs.mkdirSync(parentDir, {recursive: true});
  }
}

Hier sehen wir ensureParentDirectory() in Aktion (Zeile A)

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
  ]
);
const filePath = 'dir/sub/subsub/new-file.txt';
ensureParentDirectory(filePath); // (A)
fs.writeFileSync(filePath, 'content', {encoding: 'utf-8'});
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/sub',
    'dir/sub/subsub',
    'dir/sub/subsub/new-file.txt',
  ]
);

8.4.4 Ein temporäres Verzeichnis erstellen

fs.mkdtempSync(pathPrefix, options?) erstellt ein temporäres Verzeichnis: Es hängt 6 zufällige Zeichen an pathPrefix an, erstellt ein Verzeichnis unter dem neuen Pfad und gibt diesen Pfad zurück.

pathPrefix sollte nicht mit einem Großbuchstaben „X“ enden, da einige Plattformen nachfolgende X durch zufällige Zeichen ersetzen.

Wenn wir unser temporäres Verzeichnis innerhalb eines betriebssystemspezifischen globalen temporären Verzeichnisses erstellen möchten, können wir die Funktion os.tmpdir() verwenden

import * as os from 'node:os';
import * as path from 'node:path';

const pathPrefix = path.resolve(os.tmpdir(), 'my-app');
  // e.g. '/var/folders/ph/sz0384m11vxf/T/my-app'

const tmpPath = fs.mkdtempSync(pathPrefix);
  // e.g. '/var/folders/ph/sz0384m11vxf/T/my-app1QXOXP'

Es ist wichtig zu beachten, dass temporäre Verzeichnisse nicht automatisch gelöscht werden, wenn ein Node.js-Skript beendet wird. Wir müssen es entweder selbst löschen oder uns darauf verlassen, dass das Betriebssystem sein globales temporäres Verzeichnis regelmäßig bereinigt (was es tun kann oder auch nicht).

8.5 Dateien oder Verzeichnisse kopieren, umbenennen, verschieben

8.5.1 Dateien oder Verzeichnisse kopieren

fs.cpSync(srcPath, destPath, options?): kopiert eine Datei oder ein Verzeichnis von srcPath nach destPath. Interessante Optionen

Dies ist die Funktion in Aktion

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir-orig',
    'dir-orig/some-file.txt',
  ]
);
fs.cpSync('dir-orig', 'dir-copy', {recursive: true});
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir-copy',
    'dir-copy/some-file.txt',
    'dir-orig',
    'dir-orig/some-file.txt',
  ]
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.5.2 Dateien oder Verzeichnisse umbenennen oder verschieben

fs.renameSync(oldPath, newPath) benennt eine Datei oder ein Verzeichnis von oldPath nach newPath um oder verschiebt es.

Verwenden wir diese Funktion, um ein Verzeichnis umzubenennen

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'old-dir-name',
    'old-dir-name/some-file.txt',
  ]
);
fs.renameSync('old-dir-name', 'new-dir-name');
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'new-dir-name',
    'new-dir-name/some-file.txt',
  ]
);

Hier verwenden wir die Funktion, um eine Datei zu verschieben

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/subdir',
    'dir/subdir/some-file.txt',
  ]
);
fs.renameSync('dir/subdir/some-file.txt', 'some-file.txt');
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/subdir',
    'some-file.txt',
  ]
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.6 Dateien oder Verzeichnisse entfernen

8.6.1 Dateien und beliebige Verzeichnisse entfernen (Shell: rm, rm -r)

fs.rmSync(thePath, options?) entfernt eine Datei oder ein Verzeichnis unter thePath. Interessante Optionen

Lassen Sie uns fs.rmSync() zum Entfernen einer Datei verwenden

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/some-file.txt',
  ]
);
fs.rmSync('dir/some-file.txt');
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
  ]
);

Hier verwenden wir fs.rmSync(), um ein nicht leeres Verzeichnis rekursiv zu entfernen.

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/subdir',
    'dir/subdir/some-file.txt',
  ]
);
fs.rmSync('dir/subdir', {recursive: true});
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
  ]
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.6.2 Ein leeres Verzeichnis entfernen (Shell: rmdir)

fs.rmdirSync(thePath, options?) entfernt ein leeres Verzeichnis (es wird eine Ausnahme ausgelöst, wenn ein Verzeichnis nicht leer ist).

Der folgende Code zeigt, wie diese Funktion funktioniert

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/subdir',
  ]
);
fs.rmdirSync('dir/subdir');
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
  ]
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.6.3 Verzeichnisse leeren

Ein Skript, das seine Ausgabe in ein Verzeichnis dir speichert, muss dir oft vor Beginn leeren: Alle Dateien in dir entfernen, sodass es leer ist. Die folgende Funktion erledigt dies.

import * as path from 'node:path';

function clearDirectory(dirPath) {
  for (const fileName of fs.readdirSync(dirPath)) {
    const pathName = path.join(dirPath, fileName);
    fs.rmSync(pathName, {recursive: true});
  }
}

Wir haben zwei Dateisystemfunktionen verwendet

Dies ist ein Beispiel für die Verwendung von clearDirectory()

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/dir-file.txt',
    'dir/subdir',
    'dir/subdir/subdir-file.txt'
  ]
);
clearDirectory('dir');
assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
  ]
);

8.6.4 Dateien oder Verzeichnisse in den Papierkorb verschieben

Die Bibliothek trash verschiebt Dateien und Ordner in den Papierkorb. Sie funktioniert unter macOS, Windows und Linux (wo die Unterstützung begrenzt ist und Hilfe benötigt wird). Dies ist ein Beispiel aus ihrer Readme-Datei

import trash from 'trash';

await trash(['*.png', '!rainbow.png']);

trash() akzeptiert entweder ein Array von Strings oder einen einzelnen String als ersten Parameter. Jeder String kann ein Glob-Muster sein (mit Sternchen und anderen Metazeichen).

8.7 Dateisystemeinträge lesen und ändern

8.7.1 Prüfen, ob eine Datei oder ein Verzeichnis existiert

fs.existsSync(thePath) gibt true zurück, wenn eine Datei oder ein Verzeichnis unter thePath existiert

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/some-file.txt',
  ]
);
assert.equal(
  fs.existsSync('dir'), true
);
assert.equal(
  fs.existsSync('dir/some-file.txt'), true
);
assert.equal(
  fs.existsSync('dir/non-existent-file.txt'), false
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.7.2 Die Statistiken einer Datei prüfen: Ist es ein Verzeichnis? Wann wurde es erstellt? Etc.

fs.statSync(thePath, options?) gibt eine Instanz von fs.Stats mit Informationen über die Datei oder das Verzeichnis unter thePath zurück.

Interessante options

Eigenschaften von Instanzen von fs.Stats

Im folgenden Beispiel verwenden wir fs.statSync(), um eine Funktion isDirectory() zu implementieren

function isDirectory(thePath) {
  const stats = fs.statSync(thePath, {throwIfNoEntry: false});
  return stats !== undefined && stats.isDirectory();
}

assert.deepEqual(
  Array.from(traverseDirectory('.')),
  [
    'dir',
    'dir/some-file.txt',
  ]
);

assert.equal(
  isDirectory('dir'), true
);
assert.equal(
  isDirectory('dir/some-file.txt'), false
);
assert.equal(
  isDirectory('non-existent-dir'), false
);

Die Funktion traverseDirectory(dirPath) listet alle Nachkommen des Verzeichnisses unter dirPath auf.

8.7.3 Dateiattribute ändern: Berechtigungen, Besitzer, Gruppe, Zeitstempel

Werfen wir einen kurzen Blick auf Funktionen zum Ändern von Dateiattributen

Funktionen für die Arbeit mit Hardlinks

Funktionen für die Arbeit mit symbolischen Links

Die folgenden Funktionen arbeiten mit symbolischen Links, ohne sie aufzulösen (beachten Sie das Präfix „l“)

Weitere nützliche Funktionen

Optionen von Funktionen, die beeinflussen, wie symbolische Links behandelt werden

8.9 Weiterführende Lektüre