'node:os'path.format(): Erstellen von Pfaden aus Teilenfile:-URLs zum Referenzieren von DateienIn diesem Kapitel lernen wir, wie man mit Dateisystempfaden und Datei-URLs in Node.js arbeitet.
In diesem Kapitel untersuchen wir pfadbezogene Funktionalität in Node.js.
'node:path'.process hat Methoden zum Ändern des aktuellen Arbeitsverzeichnisses (was das ist, wird bald erklärt).'node:os' hat Funktionen, die die Pfade wichtiger Verzeichnisse zurückgeben.Das Modul 'node:path' wird oft wie folgt importiert:
import * as path from 'node:path';In diesem Kapitel wird diese Importanweisung gelegentlich weggelassen. Wir lassen auch den folgenden Import weg:
import * as assert from 'node:assert/strict';Wir können auf die Pfad-API von Node auf drei Arten zugreifen:
Wir können plattformspezifische Versionen der API aufrufen:
path.posix unterstützt Unix-Systeme einschließlich macOS.path.win32 unterstützt Windows.path selbst unterstützt immer die aktuelle Plattform. Zum Beispiel diese REPL-Interaktion auf macOS:
> path.parse === path.posix.parse
trueSehen wir uns an, wie sich die Funktion path.parse(), die Dateisystempfade parst, für die beiden Plattformen unterscheidet:
> path.win32.parse(String.raw`C:\Users\jane\file.txt`)
{
dir: 'C:\\Users\\jane',
root: 'C:\\',
base: 'file.txt',
name: 'file',
ext: '.txt',
}
> path.posix.parse(String.raw`C:\Users\jane\file.txt`)
{
dir: '',
root: '',
base: 'C:\\Users\\jane\\file.txt',
name: 'C:\\Users\\jane\\file',
ext: '.txt',
}Wir parsen einen Windows-Pfad – zuerst korrekt über die path.win32 API, dann über die path.posix API. Wir sehen, dass im letzteren Fall der Pfad nicht korrekt in seine Teile aufgeteilt wird – zum Beispiel sollte der Basisname der Datei file.txt sein (mehr dazu, was die anderen Eigenschaften bedeuten, später).
Terminologie
Ein nicht leerer Pfad besteht aus einem oder mehreren Pfadsegmenten – meist Namen von Verzeichnissen oder Dateien.
Ein Pfadtrennzeichen wird verwendet, um zwei benachbarte Pfadsegmente in einem Pfad zu trennen. path.sep enthält das Pfadtrennzeichen der aktuellen Plattform:
assert.equal(
path.posix.sep, '/' // Path separator on Unix
);
assert.equal(
path.win32.sep, '\\' // Path separator on Windows
);Ein Pfadtrennzeichen (Delimiter) trennt Elemente in Pfadlisten. path.delimiter enthält das Pfadtrennzeichen der aktuellen Plattform:
assert.equal(
path.posix.delimiter, ':' // Path delimiter on Unix
);
assert.equal(
path.win32.delimiter, ';' // Path delimiter on Windows
);Wir können Pfadtrennzeichen und Pfadtrennzeichen (Delimiter) sehen, wenn wir die PATH-Shell-Variable untersuchen – die die Pfade enthält, in denen das Betriebssystem nach ausführbaren Dateien sucht, wenn ein Befehl in einer Shell eingegeben wird.
Dies ist ein Beispiel für einen macOS PATH (Shell-Variable $PATH):
> process.env.PATH.split(/(?<=:)/)
[
'/opt/homebrew/bin:',
'/opt/homebrew/sbin:',
'/usr/local/bin:',
'/usr/bin:',
'/bin:',
'/usr/sbin:',
'/sbin',
]Der Trenner für das Aufteilen hat eine Länge von Null, da die Lookbehind-Assertion (?<=:) übereinstimmt, wenn eine gegebene Position einem Doppelpunkt vorangestellt ist, aber nichts erfasst. Daher ist das Pfadtrennzeichen ':' im vorherigen Pfad enthalten.
Dies ist ein Beispiel für einen Windows PATH (Shell-Variable %Path%):
> process.env.Path.split(/(?<=;)/)
[
'C:\\Windows\\system32;',
'C:\\Windows;',
'C:\\Windows\\System32\\Wbem;',
'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;',
'C:\\Windows\\System32\\OpenSSH\\;',
'C:\\ProgramData\\chocolatey\\bin;',
'C:\\Program Files\\nodejs\\',
]Viele Shells haben das Konzept des aktuellen Arbeitsverzeichnisses (CWD) – „das Verzeichnis, in dem ich mich gerade befinde“.
cd.process ist eine globale Node.js-Variable. Sie bietet uns Methoden zum Abrufen und Festlegen des CWD:
process.cwd() gibt das CWD zurück.process.chdir(dirPath) ändert das CWD zu dirPath.dirPath muss ein Verzeichnis vorhanden sein.Node.js verwendet das CWD, um fehlende Teile zu ergänzen, wenn ein Pfad nicht vollständig qualifiziert (vollständig) ist. Das ermöglicht es uns, teilweise qualifizierte Pfade mit verschiedenen Funktionen zu verwenden – z. B. fs.readFileSync().
Der folgende Code demonstriert process.chdir() und process.cwd() unter Unix:
process.chdir('/home/jane');
assert.equal(
process.cwd(), '/home/jane'
);Bisher haben wir das aktuelle Arbeitsverzeichnis unter Unix verwendet. Windows funktioniert anders:
Wir können path.chdir() verwenden, um beides gleichzeitig festzulegen:
process.chdir('C:\\Windows');
process.chdir('Z:\\tmp');Wenn wir zu einem Laufwerk zurückkehren, merkt sich Node.js das vorherige aktuelle Verzeichnis dieses Laufwerks.
assert.equal(
process.cwd(), 'Z:\\tmp'
);
process.chdir('C:');
assert.equal(
process.cwd(), 'C:\\Windows'
);Unix kennt nur zwei Arten von Pfaden:
Absolute Pfade sind vollständig qualifiziert und beginnen mit einem Schrägstrich.
/home/john/projRelative Pfade sind teilweise qualifiziert und beginnen mit einem Dateinamen oder einem Punkt.
. (current directory)
.. (parent directory)
dir
./dir
../dir
../../dir/subdirVerwenden wir path.resolve() (das später detaillierter erklärt wird), um relative Pfade gegen absolute Pfade aufzulösen. Die Ergebnisse sind absolute Pfade:
> const abs = '/home/john/proj';
> path.resolve(abs, '.')
'/home/john/proj'
> path.resolve(abs, '..')
'/home/john'
> path.resolve(abs, 'dir')
'/home/john/proj/dir'
> path.resolve(abs, './dir')
'/home/john/proj/dir'
> path.resolve(abs, '../dir')
'/home/john/dir'
> path.resolve(abs, '../../dir/subdir')
'/home/dir/subdir'Windows unterscheidet vier Arten von Pfaden (weitere Informationen finden Sie in der Microsoft-Dokumentation):
Absolute Pfade mit Laufwerksbuchstaben sind vollständig qualifiziert. Alle anderen Pfade sind teilweise qualifiziert.
Auflösen eines absoluten Pfades ohne Laufwerksbuchstaben gegen einen vollständig qualifizierten Pfad full, übernimmt den Laufwerksbuchstaben von full.
> const full = 'C:\\Users\\jane\\proj';
> path.resolve(full, '\\Windows')
'C:\\Windows'Auflösen eines relativen Pfades ohne Laufwerksbuchstaben gegen einen vollständig qualifizierten Pfad kann als Aktualisierung des letzteren betrachtet werden.
> const full = 'C:\\Users\\jane\\proj';
> path.resolve(full, '.')
'C:\\Users\\jane\\proj'
> path.resolve(full, '..')
'C:\\Users\\jane'
> path.resolve(full, 'dir')
'C:\\Users\\jane\\proj\\dir'
> path.resolve(full, '.\\dir')
'C:\\Users\\jane\\proj\\dir'
> path.resolve(full, '..\\dir')
'C:\\Users\\jane\\dir'
> path.resolve(full, '..\\..\\dir')
'C:\\Users\\dir'Auflösen eines relativen Pfades rel mit einem Laufwerksbuchstaben gegen einen vollständig qualifizierten Pfad full hängt vom Laufwerksbuchstaben von rel ab:
full? Löse rel gegen full auf.full? Löse rel gegen das aktuelle Verzeichnis des Laufwerks von rel auf.Das sieht folgendermaßen aus:
// Configure current directories for C: and Z:
process.chdir('C:\\Windows\\System');
process.chdir('Z:\\tmp');
const full = 'C:\\Users\\jane\\proj';
// Same drive letter
assert.equal(
path.resolve(full, 'C:dir'),
'C:\\Users\\jane\\proj\\dir'
);
assert.equal(
path.resolve(full, 'C:'),
'C:\\Users\\jane\\proj'
);
// Different drive letter
assert.equal(
path.resolve(full, 'Z:dir'),
'Z:\\tmp\\dir'
);
assert.equal(
path.resolve(full, 'Z:'),
'Z:\\tmp'
);'node:os'Das Modul 'node:os' stellt uns die Pfade von zwei wichtigen Verzeichnissen zur Verfügung:
os.homedir() gibt den Pfad zum Home-Verzeichnis des aktuellen Benutzers zurück – zum Beispiel:
> os.homedir() // macOS
'/Users/rauschma'
> os.homedir() // Windows
'C:\\Users\\axel'os.tmpdir() gibt den Pfad zum Verzeichnis des Betriebssystems für temporäre Dateien zurück – zum Beispiel:
> os.tmpdir() // macOS
'/var/folders/ph/sz0384m11vxf5byk12fzjms40000gn/T'
> os.tmpdir() // Windows
'C:\\Users\\axel\\AppData\\Local\\Temp'Es gibt zwei Funktionen zum Verketten von Pfaden:
path.resolve() gibt immer vollständig qualifizierte Pfade zurück.path.join() behält relative Pfade bei.path.resolve(): Verketten von Pfaden zur Erstellung vollständig qualifizierter Pfadepath.resolve(...paths: Array<string>): stringVerkettet die paths und gibt einen vollständig qualifizierten Pfad zurück. Es verwendet den folgenden Algorithmus:
path[0] gegen das vorherige Ergebnis auf.path[1] gegen das vorherige Ergebnis auf.Ohne Argumente gibt path.resolve() den Pfad des aktuellen Arbeitsverzeichnisses zurück:
> process.cwd()
'/usr/local'
> path.resolve()
'/usr/local'Ein oder mehrere relative Pfade werden zur Auflösung verwendet, beginnend mit dem aktuellen Arbeitsverzeichnis:
> path.resolve('.')
'/usr/local'
> path.resolve('..')
'/usr'
> path.resolve('bin')
'/usr/local/bin'
> path.resolve('./bin', 'sub')
'/usr/local/bin/sub'
> path.resolve('../lib', 'log')
'/usr/lib/log'Jeder vollständig qualifizierte Pfad ersetzt das vorherige Ergebnis.
> path.resolve('bin', '/home')
'/home'Das ermöglicht es uns, teilweise qualifizierte Pfade gegen vollständig qualifizierte Pfade aufzulösen:
> path.resolve('/home/john', 'proj', 'src')
'/home/john/proj/src'path.join(): Verketten von Pfaden unter Beibehaltung relativer Pfadepath.join(...paths: Array<string>): stringBeginnt mit paths[0] und interpretiert die verbleibenden Pfade als Anweisungen zum Auf- oder Absteigen. Im Gegensatz zu path.resolve() behält diese Funktion teilweise qualifizierte Pfade bei: Wenn paths[0] teilweise qualifiziert ist, ist das Ergebnis teilweise qualifiziert. Wenn es vollständig qualifiziert ist, ist das Ergebnis vollständig qualifiziert.
Beispiele für das Absteigen:
> path.posix.join('/usr/local', 'sub', 'subsub')
'/usr/local/sub/subsub'
> path.posix.join('relative/dir', 'sub', 'subsub')
'relative/dir/sub/subsub'Doppelpunkte steigen auf:
> path.posix.join('/usr/local', '..')
'/usr'
> path.posix.join('relative/dir', '..')
'relative'Einzelpunkte tun nichts:
> path.posix.join('/usr/local', '.')
'/usr/local'
> path.posix.join('relative/dir', '.')
'relative/dir'Wenn Argumente nach dem ersten vollständig qualifizierte Pfade sind, werden sie als relative Pfade interpretiert:
> path.posix.join('dir', '/tmp')
'dir/tmp'
> path.win32.join('dir', 'C:\\Users')
'dir\\C:\\Users'Verwendung von mehr als zwei Argumenten:
> path.posix.join('/usr/local', '../lib', '.', 'log')
'/usr/lib/log'path.normalize(): Sicherstellen, dass Pfade normalisiert sindpath.normalize(path: string): stringUnter Unix normalisiert path.normalize():
.) sind...) sind.Zum Beispiel
// Fully qualified path
assert.equal(
path.posix.normalize('/home/./john/lib/../photos///pet'),
'/home/john/photos/pet'
);
// Partially qualified path
assert.equal(
path.posix.normalize('./john/lib/../photos///pet'),
'john/photos/pet'
);Unter Windows normalisiert path.normalize():
.) sind...) sind./) – der gültig ist – in das bevorzugte Pfadtrennzeichen (\) um.Zum Beispiel
// Fully qualified path
assert.equal(
path.win32.normalize('C:\\Users/jane\\doc\\..\\proj\\\\src'),
'C:\\Users\\jane\\proj\\src'
);
// Partially qualified path
assert.equal(
path.win32.normalize('.\\jane\\doc\\..\\proj\\\\src'),
'jane\\proj\\src'
);Beachten Sie, dass path.join() mit einem einzigen Argument ebenfalls normalisiert und wie path.normalize() funktioniert:
> path.posix.normalize('/home/./john/lib/../photos///pet')
'/home/john/photos/pet'
> path.posix.join('/home/./john/lib/../photos///pet')
'/home/john/photos/pet'
> path.posix.normalize('./john/lib/../photos///pet')
'john/photos/pet'
> path.posix.join('./john/lib/../photos///pet')
'john/photos/pet'path.resolve() (ein Argument): Sicherstellen, dass Pfade normalisiert und vollständig qualifiziert sindWir sind bereits auf path.resolve() gestoßen. Mit einem einzigen Argument aufgerufen, normalisiert es Pfade und stellt sicher, dass sie vollständig qualifiziert sind.
Verwendung von path.resolve() unter Unix:
> process.cwd()
'/usr/local'
> path.resolve('/home/./john/lib/../photos///pet')
'/home/john/photos/pet'
> path.resolve('./john/lib/../photos///pet')
'/usr/local/john/photos/pet'Verwendung von path.resolve() unter Windows:
> process.cwd()
'C:\\Windows\\System'
> path.resolve('C:\\Users/jane\\doc\\..\\proj\\\\src')
'C:\\Users\\jane\\proj\\src'
> path.resolve('.\\jane\\doc\\..\\proj\\\\src')
'C:\\Windows\\System\\jane\\proj\\src'path.relative(): Erstellen relativer Pfadepath.relative(sourcePath: string, destinationPath: string): stringGibt einen relativen Pfad zurück, der uns von sourcePath zu destinationPath bringt:
> path.posix.relative('/home/john/', '/home/john/proj/my-lib/README.md')
'proj/my-lib/README.md'
> path.posix.relative('/tmp/proj/my-lib/', '/tmp/doc/zsh.txt')
'../../doc/zsh.txt'Unter Windows erhalten wir einen vollständig qualifizierten Pfad, wenn sourcePath und destinationPath auf unterschiedlichen Laufwerken liegen:
> path.win32.relative('Z:\\tmp\\', 'C:\\Users\\Jane\\')
'C:\\Users\\Jane'Diese Funktion funktioniert auch mit relativen Pfaden:
> path.posix.relative('proj/my-lib/', 'doc/zsh.txt')
'../../doc/zsh.txt'path.parse(): Erstellen eines Objekts mit Pfadteilentype PathObject = {
dir: string,
root: string,
base: string,
name: string,
ext: string,
};
path.parse(path: string): PathObjectExtrahiert verschiedene Teile von path und gibt sie in einem Objekt mit den folgenden Eigenschaften zurück:
.base: Das letzte Segment eines Pfades..ext: Die Dateinamenerweiterung der Basis..name: Die Basis ohne die Erweiterung. Dieser Teil wird auch als Stamm eines Pfades bezeichnet..root: Der Anfang eines Pfades (vor dem ersten Segment)..dir: Das Verzeichnis, in dem sich die Basis befindet – der Pfad ohne die Basis.Später sehen wir die Funktion path.format(), die das Gegenteil von path.parse() ist: Sie konvertiert ein Objekt mit Pfadteilen in einen Pfad.
path.parse() unter UnixSo sieht die Verwendung von path.parse() unter Unix aus:
> path.posix.parse('/home/jane/file.txt')
{
dir: '/home/jane',
root: '/',
base: 'file.txt',
name: 'file',
ext: '.txt',
}Das folgende Diagramm visualisiert den Umfang der Teile:
/ home/jane / file .txt
| root | | name | ext |
| dir | base |
Zum Beispiel sehen wir, dass .dir der Pfad ohne die Basis ist. Und dass .base gleich .name plus .ext ist.
path.parse() unter WindowsSo funktioniert path.parse() unter Windows:
> path.win32.parse(String.raw`C:\Users\john\file.txt`)
{
dir: 'C:\\Users\\john',
root: 'C:\\',
base: 'file.txt',
name: 'file',
ext: '.txt',
}Dies ist ein Diagramm für das Ergebnis:
C:\ Users\john \ file .txt
| root | | name | ext |
| dir | base |
path.basename(): Extrahieren der Basis eines Pfadespath.basename(path, ext?)Gibt die Basis von path zurück:
> path.basename('/home/jane/file.txt')
'file.txt'Optional kann diese Funktion auch ein Suffix entfernen:
> path.basename('/home/jane/file.txt', '.txt')
'file'
> path.basename('/home/jane/file.txt', 'txt')
'file.'
> path.basename('/home/jane/file.txt', 'xt')
'file.t'Das Entfernen der Erweiterung ist Case-sensitiv – sogar unter Windows!
> path.win32.basename(String.raw`C:\Users\john\file.txt`, '.txt')
'file'
> path.win32.basename(String.raw`C:\Users\john\file.txt`, '.TXT')
'file.txt'path.dirname(): Extrahieren des übergeordneten Verzeichnisses eines Pfadespath.dirname(path)Gibt das übergeordnete Verzeichnis der Datei oder des Verzeichnisses unter path zurück:
> path.win32.dirname(String.raw`C:\Users\john\file.txt`)
'C:\\Users\\john'
> path.win32.dirname('C:\\Users\\john\\dir\\')
'C:\\Users\\john'
> path.posix.dirname('/home/jane/file.txt')
'/home/jane'
> path.posix.dirname('/home/jane/dir/')
'/home/jane'path.extname(): Extrahieren der Erweiterung eines Pfadespath.extname(path)Gibt die Erweiterung von path zurück:
> path.extname('/home/jane/file.txt')
'.txt'
> path.extname('/home/jane/file.')
'.'
> path.extname('/home/jane/file')
''
> path.extname('/home/jane/')
''
> path.extname('/home/jane')
''path.isAbsolute(): Ist ein gegebener Pfad absolut?path.isAbsolute(path: string): booleanGibt true zurück, wenn path absolut ist, und false andernfalls.
Die Ergebnisse unter Unix sind unkompliziert:
> path.posix.isAbsolute('/home/john')
true
> path.posix.isAbsolute('john')
falseUnter Windows bedeutet „absolut“ nicht unbedingt „vollständig qualifiziert“ (nur der erste Pfad ist vollständig qualifiziert):
> path.win32.isAbsolute('C:\\Users\\jane')
true
> path.win32.isAbsolute('\\Users\\jane')
true
> path.win32.isAbsolute('C:jane')
false
> path.win32.isAbsolute('jane')
falsepath.format(): Erstellen von Pfaden aus Teilentype PathObject = {
dir: string,
root: string,
base: string,
name: string,
ext: string,
};
path.format(pathObject: PathObject): stringErstellt einen Pfad aus einem Pfadobjekt:
> path.format({dir: '/home/jane', base: 'file.txt'})
'/home/jane/file.txt'Wir können path.format() verwenden, um die Erweiterung eines Pfades zu ändern:
function changeFilenameExtension(pathStr, newExtension) {
if (!newExtension.startsWith('.')) {
throw new Error(
'Extension must start with a dot: '
+ JSON.stringify(newExtension)
);
}
const parts = path.parse(pathStr);
return path.format({
...parts,
base: undefined, // prevent .base from overriding .name and .ext
ext: newExtension,
});
}
assert.equal(
changeFilenameExtension('/tmp/file.md', '.html'),
'/tmp/file.html'
);
assert.equal(
changeFilenameExtension('/tmp/file', '.html'),
'/tmp/file.html'
);
assert.equal(
changeFilenameExtension('/tmp/file/', '.html'),
'/tmp/file.html'
);Wenn wir die ursprüngliche Dateinamenerweiterung kennen, können wir auch einen regulären Ausdruck verwenden, um die Dateinamenerweiterung zu ändern:
> '/tmp/file.md'.replace(/\.md$/i, '.html')
'/tmp/file.html'
> '/tmp/file.MD'.replace(/\.md$/i, '.html')
'/tmp/file.html'Manchmal möchten wir dieselben Pfade auf verschiedenen Plattformen verwenden. Dann stehen wir vor zwei Problemen:
Betrachten wir als Beispiel eine Node.js-App, die auf einem Verzeichnis mit Daten operiert. Nehmen wir an, die App kann mit zwei Arten von Pfaden konfiguriert werden:
Aufgrund der oben genannten Probleme:
Wir können keine vollständig qualifizierten Pfade zwischen Plattformen wiederverwenden.
Wir können Pfade wiederverwenden, die in das Datenverzeichnis zeigen. Solche Pfade können in Konfigurationsdateien (innerhalb oder außerhalb des Datenverzeichnisses) und in Konstanten im Code der App gespeichert werden. Um das zu tun:
Der nächste Abschnitt erklärt, wie beides erreicht werden kann.
Relative plattformunabhängige Pfade können als Arrays von Pfadsegmenten gespeichert und wie folgt in vollständig qualifizierte plattformspezifische Pfade umgewandelt werden:
const universalRelativePath = ['static', 'img', 'logo.jpg'];
const dataDirUnix = '/home/john/data-dir';
assert.equal(
path.posix.resolve(dataDirUnix, ...universalRelativePath),
'/home/john/data-dir/static/img/logo.jpg'
);
const dataDirWindows = 'C:\\Users\\jane\\data-dir';
assert.equal(
path.win32.resolve(dataDirWindows, ...universalRelativePath),
'C:\\Users\\jane\\data-dir\\static\\img\\logo.jpg'
);Um relative plattformspezifische Pfade zu erstellen, können wir verwenden:
const dataDir = '/home/john/data-dir';
const pathInDataDir = '/home/john/data-dir/static/img/logo.jpg';
assert.equal(
path.relative(dataDir, pathInDataDir),
'static/img/logo.jpg'
);Die folgende Funktion konvertiert relative plattformspezifische Pfade in plattformunabhängige Pfade:
import * as path from 'node:path';
function splitRelativePathIntoSegments(relPath) {
if (path.isAbsolute(relPath)) {
throw new Error('Path isn’t relative: ' + relPath);
}
relPath = path.normalize(relPath);
const result = [];
while (true) {
const base = path.basename(relPath);
if (base.length === 0) break;
result.unshift(base);
const dir = path.dirname(relPath);
if (dir === '.') break;
relPath = dir;
}
return result;
}Verwendung von splitRelativePathIntoSegments() unter Unix:
> splitRelativePathIntoSegments('static/img/logo.jpg')
[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')
[ 'file.txt' ]Verwendung von splitRelativePathIntoSegments() unter Windows:
> splitRelativePathIntoSegments('static/img/logo.jpg')
[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('C:static/img/logo.jpg')
[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')
[ 'file.txt' ]
> splitRelativePathIntoSegments('C:file.txt')
[ 'file.txt' ]Das npm-Modul 'minimatch' ermöglicht es uns, Pfade gegen Muster abzugleichen, die als Glob-Ausdrücke, Glob-Muster oder Globs bezeichnet werden:
import minimatch from 'minimatch';
assert.equal(
minimatch('/dir/sub/file.txt', '/dir/sub/*.txt'), true
);
assert.equal(
minimatch('/dir/sub/file.txt', '/**/file.txt'), true
);Anwendungsfälle für Globs:
Weitere Glob-Bibliotheken:
Die gesamte API von minimatch ist in der Readme-Datei des Projekts dokumentiert. In diesem Unterabschnitt betrachten wir die wichtigste Funktionalität.
Minimatch kompiliert Globs in JavaScript-RegExp-Objekte und verwendet diese zum Abgleichen.
minimatch(): Kompilieren und einmaliges Abgleichenminimatch(path: string, glob: string, options?: MinimatchOptions): booleanGibt true zurück, wenn glob mit path übereinstimmt, und false andernfalls.
Zwei interessante Optionen:
.dot: boolean (Standard: false)
Wenn true, stimmen Wildcardsymbole wie * und ** mit „unsichtbaren“ Pfadsegmenten überein (deren Namen mit Punkten beginnen).
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json')
false
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json', {dot: true})
true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt')
false
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt', {dot: true})
true.matchBase: boolean (Standard: false)
Wenn true, wird ein Muster ohne Schrägstriche mit dem Basisnamen eines Pfades abgeglichen.
> minimatch('/dir/file.txt', 'file.txt')
false
> minimatch('/dir/file.txt', 'file.txt', {matchBase: true})
truenew minimatch.Minimatch(): Einmaliges Kompilieren, mehrmaliges AbgleichenDie Klasse minimatch.Minimatch ermöglicht es uns, den Glob nur einmal in einen regulären Ausdruck zu kompilieren und mehrmals abzugleichen:
new Minimatch(pattern: string, options?: MinimatchOptions)So wird diese Klasse verwendet:
import minimatch from 'minimatch';
const {Minimatch} = minimatch;
const glob = new Minimatch('/dir/sub/*.txt');
assert.equal(
glob.match('/dir/sub/file.txt'), true
);
assert.equal(
glob.match('/dir/sub/notes.txt'), true
);Dieser Unterabschnitt behandelt die Grundlagen der Syntax. Aber es gibt weitere Funktionen. Diese sind hier dokumentiert:
Auch unter Windows werden Glob-Segmente durch Schrägstriche getrennt – sie stimmen aber sowohl mit Backslashes als auch mit Schrägstrichen überein (die unter Windows gültige Pfadtrennzeichen sind).
> minimatch('dir\\sub/file.txt', 'dir/sub/file.txt')
trueMinimatch normalisiert Pfade nicht für uns:
> minimatch('./file.txt', './file.txt')
true
> minimatch('./file.txt', 'file.txt')
false
> minimatch('file.txt', './file.txt')
falseDaher müssen wir Pfade normalisieren, wenn wir sie nicht selbst erstellen:
> path.normalize('./file.txt')
'file.txt'Muster ohne Wildcard-Symbole (die flexibler abgleichen) müssen exakt übereinstimmen. Insbesondere müssen die Pfadtrennzeichen übereinstimmen:
> minimatch('/dir/file.txt', '/dir/file.txt')
true
> minimatch('dir/file.txt', 'dir/file.txt')
true
> minimatch('/dir/file.txt', 'dir/file.txt')
false
> minimatch('/dir/file.txt', 'file.txt')
falseDas heißt, wir müssen uns entweder für absolute oder relative Pfade entscheiden.
Mit der Option .matchBase können wir Muster ohne Schrägstriche mit den Basisnamen von Pfaden abgleichen:
> minimatch('/dir/file.txt', 'file.txt', {matchBase: true})
true*) gleicht ein einzelnes Segment (oder einen Teil davon) abDas Wildcard-Symbol Sternchen (*) gleicht jedes Pfadsegment oder jeden Teil eines Segments ab:
> minimatch('/dir/file.txt', '/*/file.txt')
true
> minimatch('/tmp/file.txt', '/*/file.txt')
true
> minimatch('/dir/file.txt', '/dir/*.txt')
true
> minimatch('/dir/data.txt', '/dir/*.txt')
trueDas Sternchen gleicht keine „unsichtbaren Dateien“ ab, deren Namen mit Punkten beginnen. Wenn wir diese abgleichen wollen, müssen wir das Sternchen mit einem Punkt versehen:
> minimatch('file.txt', '*')
true
> minimatch('.gitignore', '*')
false
> minimatch('.gitignore', '.*')
true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt')
falseDie Option .dot ermöglicht es uns, dieses Verhalten zu deaktivieren:
> minimatch('.gitignore', '*', {dot: true})
true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt', {dot: true})
true**) gleicht null oder mehr Segmente ab´**/ gleicht null oder mehr Segmente ab.
> minimatch('/file.txt', '/**/file.txt')
true
> minimatch('/dir/file.txt', '/**/file.txt')
true
> minimatch('/dir/sub/file.txt', '/**/file.txt')
trueWenn wir relative Pfade abgleichen wollen, darf das Muster immer noch nicht mit einem Pfadtrennzeichen beginnen:
> minimatch('file.txt', '/**/file.txt')
falseDas doppelte Sternchen gleicht keine „unsichtbaren“ Pfadsegmente ab, deren Namen mit Punkten beginnen:
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json')
falseWir können dieses Verhalten über die Option .dot deaktivieren:
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json', {dot: true})
trueWenn wir einen Glob mit einem Ausrufezeichen beginnen, gleicht er, wenn das Muster nach dem Ausrufezeichen nicht übereinstimmt:
> minimatch('file.txt', '!**/*.txt')
false
> minimatch('file.js', '!**/*.txt')
trueKomma-getrennte Muster in geschweiften Klammern stimmen überein, wenn eines der Muster übereinstimmt:
> minimatch('file.txt', 'file.{txt,js}')
true
> minimatch('file.js', 'file.{txt,js}')
trueEin Paar ganzer Zahlen, getrennt durch doppelte Punkte, definiert einen Bereich von ganzen Zahlen und stimmt überein, wenn eines seiner Elemente übereinstimmt:
> minimatch('file1.txt', 'file{1..3}.txt')
true
> minimatch('file2.txt', 'file{1..3}.txt')
true
> minimatch('file3.txt', 'file{1..3}.txt')
true
> minimatch('file4.txt', 'file{1..3}.txt')
falseAuffüllen mit Nullen wird ebenfalls unterstützt:
> minimatch('file1.txt', 'file{01..12}.txt')
false
> minimatch('file01.txt', 'file{01..12}.txt')
true
> minimatch('file02.txt', 'file{01..12}.txt')
true
> minimatch('file12.txt', 'file{01..15}.txt')
truefile:-URLs zum Referenzieren von DateienEs gibt zwei gängige Arten, in Node.js auf Dateien zu verweisen:
URL mit dem Protokoll file:.Zum Beispiel
assert.equal(
fs.readFileSync(
'/tmp/data.txt', {encoding: 'utf-8'}),
'Content'
);
assert.equal(
fs.readFileSync(
new URL('file:///tmp/data.txt'), {encoding: 'utf-8'}),
'Content'
);URLIn diesem Abschnitt befassen wir uns genauer mit der Klasse URL. Weitere Informationen zu dieser Klasse:
In diesem Kapitel greifen wir über eine globale Variable auf die Klasse URL zu, da sie auf anderen Webplattformen so verwendet wird. Sie kann aber auch importiert werden:
import {URL} from 'node:url';URLs sind eine Teilmenge von URIs. RFC 3986, der Standard für URIs, unterscheidet zwei Arten von URI-Referenzen:
URLDie Klasse URL kann auf zwei Arten instanziiert werden:
new URL(uri: string)
uri muss eine URI sein. Sie gibt die URI der neuen Instanz an.
new URL(uriRef: string, baseUri: string)
baseUri muss eine URI sein. Wenn uriRef eine relative Referenz ist, wird sie gegen baseUri aufgelöst und das Ergebnis wird zur URI der neuen Instanz.
Wenn uriRef eine URI ist, ersetzt sie baseUri vollständig als Daten, auf denen die Instanz basiert.
Hier sehen wir die Klasse in Aktion:
// If there is only one argument, it must be a proper URI
assert.equal(
new URL('https://example.com/public/page.html').toString(),
'https://example.com/public/page.html'
);
assert.throws(
() => new URL('../book/toc.html'),
/^TypeError \[ERR_INVALID_URL\]: Invalid URL$/
);
// Resolve a relative reference against a base URI
assert.equal(
new URL(
'../book/toc.html',
'https://example.com/public/page.html'
).toString(),
'https://example.com/book/toc.html'
);URLLassen Sie uns diese Variante des URL-Konstruktors erneut betrachten:
new URL(uriRef: string, baseUri: string)Das Argument baseUri wird in eine Zeichenkette umgewandelt. Daher kann jedes Objekt verwendet werden – solange es beim Umwandeln in eine Zeichenkette zu einer gültigen URL wird.
const obj = { toString() {return 'https://example.com'} };
assert.equal(
new URL('index.html', obj).href,
'https://example.com/index.html'
);Das ermöglicht es uns, relative Referenzen gegen URL-Instanzen aufzulösen:
const url = new URL('https://example.com/dir/file1.html');
assert.equal(
new URL('../file2.html', url).href,
'https://example.com/file2.html'
);So verwendet, ähnelt der Konstruktor lose path.resolve().
URL-InstanzenInstanzen von URL haben die folgenden Eigenschaften:
type URL = {
protocol: string,
username: string,
password: string,
hostname: string,
port: string,
host: string,
readonly origin: string,
pathname: string,
search: string,
readonly searchParams: URLSearchParams,
hash: string,
href: string,
toString(): string,
toJSON(): string,
}Es gibt drei gängige Möglichkeiten, URLs in Zeichenketten zu konvertieren:
const url = new URL('https://example.com/about.html');
assert.equal(
url.toString(),
'https://example.com/about.html'
);
assert.equal(
url.href,
'https://example.com/about.html'
);
assert.equal(
url.toJSON(),
'https://example.com/about.html'
);Die Methode .toJSON() ermöglicht es uns, URLs in JSON-Daten zu verwenden.
const jsonStr = JSON.stringify({
pageUrl: new URL('https://exploringjs.de')
});
assert.equal(
jsonStr, '{"pageUrl":"https://exploringjs.de"}'
);URL-EigenschaftenDie Eigenschaften von URL-Instanzen sind keine eigenen Dateneigenschaften, sie werden über Getter und Setter implementiert. Im nächsten Beispiel verwenden wir die Hilfsfunktion pickProps() (deren Code am Ende gezeigt wird), um die von diesen Gettern zurückgegebenen Werte in ein einfaches Objekt zu kopieren:
const props = pickProps(
new URL('https://jane:pw@example.com:80/news.html?date=today#misc'),
'protocol', 'username', 'password', 'hostname', 'port', 'host',
'origin', 'pathname', 'search', 'hash', 'href'
);
assert.deepEqual(
props,
{
protocol: 'https:',
username: 'jane',
password: 'pw',
hostname: 'example.com',
port: '80',
host: 'example.com:80',
origin: 'https://example.com:80',
pathname: '/news.html',
search: '?date=today',
hash: '#misc',
href: 'https://jane:pw@example.com:80/news.html?date=today#misc'
}
);
function pickProps(input, ...keys) {
const output = {};
for (const key of keys) {
output[key] = input[key];
}
return output;
}Leider ist der Pfadname eine einzelne atomare Einheit. Das heißt, wir können die Klasse URL nicht verwenden, um auf ihre Teile (Basis, Erweiterung usw.) zuzugreifen.
Wir können auch Teile einer URL ändern, indem wir Eigenschaften wie .hostname festlegen:
const url = new URL('https://example.com');
url.hostname = '2ality.com';
assert.equal(
url.href, 'https://2ality.com/'
);Wir können die Setter verwenden, um URLs aus Teilen zu erstellen (Idee von Haroen Viaene):
// Object.assign() invokes setters when transferring property values
const urlFromParts = (parts) => Object.assign(
new URL('https://example.com'), // minimal dummy URL
parts // assigned to the dummy
);
const url = urlFromParts({
protocol: 'https:',
hostname: '2ality.com',
pathname: '/p/about.html',
});
assert.equal(
url.href, 'https://2ality.com/p/about.html'
);.searchParamsWir können die Eigenschaft .searchParams verwenden, um die Suchparameter von URLs zu verwalten. Ihr Wert ist eine Instanz von URLSearchParams.
Wir können sie verwenden, um Suchparameter zu lesen:
const url = new URL('https://example.com/?topic=js');
assert.equal(
url.searchParams.get('topic'), 'js'
);
assert.equal(
url.searchParams.has('topic'), true
);Wir können Suchparameter auch über sie ändern:
url.searchParams.append('page', '5');
assert.equal(
url.href, 'https://example.com/?topic=js&page=5'
);
url.searchParams.set('topic', 'css');
assert.equal(
url.href, 'https://example.com/?topic=css&page=5'
);Es ist verlockend, zwischen Dateipfaden und URLs manuell zu konvertieren. Zum Beispiel können wir versuchen, eine URL-Instanz myUrl über myUrl.pathname in einen Dateipfad zu konvertieren. Das funktioniert jedoch nicht immer – es ist besser, diese Funktion zu verwenden:
url.fileURLToPath(url: URL | string): stringDer folgende Code vergleicht die Ergebnisse dieser Funktion mit den Werten von .pathname:
import * as url from 'node:url';
//::::: Unix :::::
const url1 = new URL('file:///tmp/with%20space.txt');
assert.equal(
url1.pathname, '/tmp/with%20space.txt');
assert.equal(
url.fileURLToPath(url1), '/tmp/with space.txt');
const url2 = new URL('file:///home/thor/Mj%C3%B6lnir.txt');
assert.equal(
url2.pathname, '/home/thor/Mj%C3%B6lnir.txt');
assert.equal(
url.fileURLToPath(url2), '/home/thor/Mjölnir.txt');
//::::: Windows :::::
const url3 = new URL('file:///C:/dir/');
assert.equal(
url3.pathname, '/C:/dir/');
assert.equal(
url.fileURLToPath(url3), 'C:\\dir\\');Diese Funktion ist das Gegenteil von url.fileURLToPath():
url.pathToFileURL(path: string): URLSie konvertiert path in eine Datei-URL.
> url.pathToFileURL('/home/john/Work Files').href
'file:///home/john/Work%20Files'Ein wichtiger Anwendungsfall für URLs ist der Zugriff auf eine Datei, die ein Geschwistermodul des aktuellen Moduls ist:
function readData() {
const url = new URL('data.txt', import.meta.url);
return fs.readFileSync(url, {encoding: 'UTF-8'});
}Diese Funktion verwendet import.meta.url, die die URL des aktuellen Moduls enthält (was in Node.js normalerweise eine file:-URL ist).
Die Verwendung von fetch() hätte den vorherigen Code noch plattformunabhängiger gemacht. Allerdings funktioniert fetch() in Node.js 18.9.0 noch nicht für file:-URLs:
> await fetch('file:///tmp/file.txt')
TypeError: fetch failed
cause: Error: not implemented... yet...Ein ESM-Modul kann auf zwei Arten verwendet werden:
Wenn wir möchten, dass ein Modul auf beide Arten verwendet werden kann, benötigen wir eine Möglichkeit zu prüfen, ob das aktuelle Modul das Hauptmodul ist, da nur dann die Skriptfunktionalität ausgeführt wird. In diesem Kapitel lernen wir, wie wir diese Prüfung durchführen.
Mit CommonJS können wir das folgende Muster verwenden, um zu erkennen, ob das aktuelle Modul der Einstiegspunkt war (Quelle: Node.js-Dokumentation):
if (require.main === module) {
// Main CommonJS module
}Derzeit haben ESM-Module keine einfache eingebaute Möglichkeit zu prüfen, ob ein Modul main ist. Stattdessen müssen wir die folgende Problemumgehung verwenden (basierend auf einem Tweet von Rich Harris):
import * as url from 'node:url';
if (import.meta.url.startsWith('file:')) { // (A)
const modulePath = url.fileURLToPath(import.meta.url);
if (process.argv[1] === modulePath) { // (B)
// Main ESM module
}
}Erläuterungen
import.meta.url enthält die URL des aktuell ausgeführten ESM-Moduls.
Wenn wir sicher sind, dass unser Code immer lokal ausgeführt wird (was in Zukunft seltener werden könnte), können wir die Prüfung in Zeile A weglassen. Wenn wir das tun und der Code nicht lokal ausgeführt wird, erhalten wir zumindest eine Ausnahme (und keinen stillen Fehler) – dank url.fileURLToPath() (siehe nächster Punkt).
Wir verwenden url.fileURLToPath(), um die URL in einen lokalen Pfad zu konvertieren. Diese Funktion löst eine Ausnahme aus, wenn das Protokoll nicht file: ist.
process.argv[1] enthält den Pfad des anfänglichen Moduls. Der Vergleich in Zeile B funktioniert, da dieser Wert immer ein absoluter Pfad ist – Node.js richtet ihn wie folgt ein (Quellcode):
process.argv[1] = path.resolve(process.argv[1]);file:-URLsWenn Shell-Skripte Verweise auf Dateien erhalten oder Verweise auf Dateien exportieren (z. B. indem sie sie auf dem Bildschirm protokollieren), sind es praktisch immer Pfade. Es gibt jedoch zwei Fälle, in denen wir URLs benötigen (wie in den vorherigen Unterabschnitten besprochen):