'node:path'
API 的三种方式'node:os'
获取标准目录的路径path.resolve()
:连接路径以创建完全限定路径path.join()
:连接路径并保留相对路径path.normalize()
:确保路径已规范化path.resolve()
(一个参数):确保路径已规范化且完全限定path.relative()
:创建相对路径path.parse()
:创建一个包含路径部分的对象path.basename()
:提取路径的基本名称path.dirname()
:提取路径的父目录path.extname()
:提取路径的扩展名path.format()
:从部分创建路径file:
URL 引用文件URL
file:
URL在本章中,我们将学习如何在 Node.js 上使用文件系统路径和文件 URL。
在本章中,我们将探讨 Node.js 上与路径相关的功能
'node:path'
中。process
具有用于更改*当前工作目录*的方法(稍后将解释这是什么)。'node:os'
具有返回重要目录路径的函数。'node:path'
API 的三种方式模块 'node:path'
通常按如下方式导入
import * as path from 'node:path';
在本章中,此导入语句有时会省略。我们还省略了以下导入
import * as assert from 'node:assert/strict';
我们可以通过三种方式访问 Node 的路径 API
我们可以访问特定于平台的 API 版本
path.posix
支持包括 macOS 在内的 Unix 系统。path.win32
支持 Windows。path
本身始终支持当前平台。例如,这是 macOS 上的 REPL 交互
> path.parse === path.posix.parsetrue
让我们看看解析文件系统路径的函数 path.parse()
在两个平台上的区别
> 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',
}
我们解析 Windows 路径 - 首先通过 path.win32
API 正确解析,然后通过 path.posix
API 解析。我们可以看到,在后一种情况下,路径没有被正确地分割成它的各个部分 - 例如,文件的基本名称应该是 file.txt
(稍后将详细介绍其他属性的含义)。
术语
非空路径由一个或多个*路径段*组成 - 通常是目录或文件的名称。
*路径分隔符*用于分隔路径中两个相邻的路径段。path.sep
包含当前平台的路径分隔符
.equal(
assert.posix.sep, '/' // Path separator on Unix
path;
).equal(
assert.win32.sep, '\\' // Path separator on Windows
path; )
*路径分隔符*用于分隔路径列表中的元素。path.delimiter
包含当前平台的路径分隔符
.equal(
assert.posix.delimiter, ':' // Path delimiter on Unix
path;
).equal(
assert.win32.delimiter, ';' // Path delimiter on Windows
path; )
如果我们检查 PATH shell 变量 - 它包含操作系统在 shell 中输入命令时查找可执行文件的路径 - 我们可以看到路径分隔符和路径分隔符。
这是一个 macOS PATH(shell 变量 $PATH
)的示例
> process.env.PATH.split(/(?<=:)/)
['/opt/homebrew/bin:',
'/opt/homebrew/sbin:',
'/usr/local/bin:',
'/usr/bin:',
'/bin:',
'/usr/sbin:',
'/sbin',
]
拆分分隔符的长度为零,因为 后向断言 (?<=:)
仅在给定位置前面是冒号时才匹配,但它不捕获任何内容。因此,路径分隔符 ':'
包含在前面的路径中。
这是一个 Windows PATH(shell 变量 %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\\',
]
许多 shell 都有*当前工作目录*(CWD)的概念 - “我当前所在的目录”
cd
。process
是一个全局 Node.js 变量。它为我们提供了获取和设置 CWD 的方法
process.cwd()
返回 CWD。process.chdir(dirPath)
将 CWD 更改为 dirPath
。dirPath
处必须有一个目录。每当路径不是*完全限定*(完整)时,Node.js 都会使用 CWD 来填充缺失的部分。这使我们能够对各种函数使用部分限定路径 - 例如 fs.readFileSync()
。
以下代码演示了 Unix 上的 process.chdir()
和 process.cwd()
process.chdir('/home/jane');
.equal(
assertprocess.cwd(), '/home/jane'
; )
到目前为止,我们一直在 Unix 上使用当前工作目录。Windows 的工作方式有所不同
我们可以使用 path.chdir()
同时设置两者
process.chdir('C:\\Windows');
process.chdir('Z:\\tmp');
当我们重新访问一个驱动器时,Node.js 会记住该驱动器之前的当前目录
.equal(
assertprocess.cwd(), 'Z:\\tmp'
;
)process.chdir('C:');
.equal(
assertprocess.cwd(), 'C:\\Windows'
; )
Unix 只知道两种路径
*绝对路径*是完全限定的,并以斜杠开头
/home/john/proj
*相对路径*是部分限定的,并以文件名或点开头
. (current directory)
.. (parent directory)
dir
./dir
../dir
../../dir/subdir
让我们使用 path.resolve()
(稍后将在 此处 详细解释)来解析相对于绝对路径的相对路径。结果是绝对路径
> 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 区分四种路径(有关更多信息,请参阅 Microsoft 文档)
带有驱动器号的绝对路径是完全限定的。所有其他路径都是部分限定的。
**解析没有驱动器号的绝对路径**时,相对于完全限定路径 full
,将使用 full
的驱动器号
> const full = 'C:\\Users\\jane\\proj';
> path.resolve(full, '\\Windows')'C:\\Windows'
**解析没有驱动器号的相对路径**时,相对于完全限定路径,可以将其视为更新后者
> 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'
**解析带有驱动器号的相对路径 rel
** 时,相对于完全限定路径 full
,取决于 rel
的驱动器号
full
相同的驱动器号?根据 full
解析 rel
。full
不同的驱动器号?根据 rel
驱动器的当前目录解析 rel
。如下所示
// 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
.equal(
assert.resolve(full, 'C:dir'),
path'C:\\Users\\jane\\proj\\dir'
;
).equal(
assert.resolve(full, 'C:'),
path'C:\\Users\\jane\\proj'
;
)
// Different drive letter
.equal(
assert.resolve(full, 'Z:dir'),
path'Z:\\tmp\\dir'
;
).equal(
assert.resolve(full, 'Z:'),
path'Z:\\tmp'
; )
'node:os'
获取标准目录的路径模块 'node:os'
为我们提供了两个重要目录的路径
os.homedir()
返回当前用户主目录的路径 - 例如
> os.homedir() // macOS'/Users/rauschma'
> os.homedir() // Windows'C:\\Users\\axel'
os.tmpdir()
返回操作系统临时文件目录的路径 - 例如
> os.tmpdir() // macOS'/var/folders/ph/sz0384m11vxf5byk12fzjms40000gn/T'
> os.tmpdir() // Windows'C:\\Users\\axel\\AppData\\Local\\Temp'
有两个函数用于连接路径
path.resolve()
始终返回完全限定路径path.join()
保留相对路径path.resolve()
:连接路径以创建完全限定路径.resolve(...paths: Array<string>): string path
连接 paths
并返回完全限定路径。它使用以下算法
path[0]
。path[1]
。不带参数时,path.resolve()
返回当前工作目录的路径
> process.cwd()'/usr/local'
> path.resolve()'/usr/local'
一个或多个相对路径用于解析,从当前工作目录开始
> 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'
任何完全限定路径都会替换上一个结果
> path.resolve('bin', '/home')'/home'
这使我们能够解析相对于完全限定路径的部分限定路径
> path.resolve('/home/john', 'proj', 'src')'/home/john/proj/src'
path.join()
:连接路径并保留相对路径.join(...paths: Array<string>): string path
从 paths[0]
开始,并将剩余路径解释为向上或向下移动的指令。与 path.resolve()
相反,此函数保留部分限定路径:如果 paths[0]
是部分限定的,则结果是部分限定的。如果它是完全限定的,则结果是完全限定的。
向下移动的示例
> path.posix.join('/usr/local', 'sub', 'subsub')'/usr/local/sub/subsub'
> path.posix.join('relative/dir', 'sub', 'subsub')'relative/dir/sub/subsub'
双点向上移动
> path.posix.join('/usr/local', '..')'/usr'
> path.posix.join('relative/dir', '..')'relative'
单点不执行任何操作
> path.posix.join('/usr/local', '.')'/usr/local'
> path.posix.join('relative/dir', '.')'relative/dir'
如果第一个参数之后的参数是完全限定路径,则将它们解释为相对路径
> path.posix.join('dir', '/tmp')'dir/tmp'
> path.win32.join('dir', 'C:\\Users')'dir\\C:\\Users'
使用两个以上的参数
> path.posix.join('/usr/local', '../lib', '.', 'log')'/usr/lib/log'
path.normalize()
:确保路径已规范化.normalize(path: string): string path
在 Unix 上,path.normalize()
.
) 路径段。..
) 路径段。例如
// Fully qualified path
.equal(
assert.posix.normalize('/home/./john/lib/../photos///pet'),
path'/home/john/photos/pet'
;
)
// Partially qualified path
.equal(
assert.posix.normalize('./john/lib/../photos///pet'),
path'john/photos/pet'
; )
在 Windows 上,path.normalize()
.
) 路径段。..
) 路径段。/
) - 这是合法的 - 转换为首选路径分隔符 (\
)。例如
// Fully qualified path
.equal(
assert.win32.normalize('C:\\Users/jane\\doc\\..\\proj\\\\src'),
path'C:\\Users\\jane\\proj\\src'
;
)
// Partially qualified path
.equal(
assert.win32.normalize('.\\jane\\doc\\..\\proj\\\\src'),
path'jane\\proj\\src'
; )
请注意,带有单个参数的 path.join()
也会规范化并与 path.normalize()
的工作方式相同
> 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()
(一个参数):确保路径已规范化且完全限定我们已经遇到过 path.resolve()
。当使用单个参数调用时,它会规范化路径并确保它们是完全限定的。
在 Unix 上使用 path.resolve()
> 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'
在 Windows 上使用 path.resolve()
> 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()
:创建相对路径.relative(sourcePath: string, destinationPath: string): string path
返回从 sourcePath
到 destinationPath
的相对路径
> 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'
在 Windows 上,如果 sourcePath
和 destinationPath
位于不同的驱动器上,我们将获得一个完全限定的路径
> path.win32.relative('Z:\\tmp\\', 'C:\\Users\\Jane\\')'C:\\Users\\Jane'
此函数也适用于相对路径
> path.posix.relative('proj/my-lib/', 'doc/zsh.txt')'../../doc/zsh.txt'
path.parse()
:创建一个包含路径部分的对象type PathObject = {
: string,
dir: string,
root: string,
base: string,
name: string,
ext;
}.parse(path: string): PathObject path
提取 path
的各个部分,并将它们返回到具有以下属性的对象中
.base
:路径的最后一个段.ext
:base 的文件名扩展名.name
:不带扩展名的 base。这部分也称为路径的*词干*。.root
:路径的开头(第一个段之前).dir
:base 所在的目录 - 不带 base 的路径稍后,我们将看到 函数 path.format()
,它是 path.parse()
的逆函数:它将包含路径部分的对象转换为路径。
path.parse()
这就是在 Unix 上使用 path.parse()
的样子
> path.posix.parse('/home/jane/file.txt'){
dir: '/home/jane',
root: '/',
base: 'file.txt',
name: 'file',
ext: '.txt',
}
下图显示了各部分的范围
/ home/jane / file .txt
| root | | name | ext |
| dir | base |
例如,我们可以看到 .dir
是不带 base 的路径。并且 .base
是 .name
加上 .ext
。
path.parse()
这就是 path.parse()
在 Windows 上的工作方式
> path.win32.parse(String.raw`C:\Users\john\file.txt`){
dir: 'C:\\Users\\john',
root: 'C:\\',
base: 'file.txt',
name: 'file',
ext: '.txt',
}
这是结果图
C:\ Users\john \ file .txt
| root | | name | ext |
| dir | base |
path.basename()
:提取路径的 base.basename(path, ext?) path
返回 path
的 base
> path.basename('/home/jane/file.txt')'file.txt'
可选地,此函数还可以删除后缀
> 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'
删除扩展名区分大小写 - 即使在 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()
:提取路径的父目录.dirname(path) path
返回 path
处文件或目录的父目录
> 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()
:提取路径的扩展名.extname(path) path
返回 path
的扩展名
> 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()
:给定的路径是绝对路径吗?.isAbsolute(path: string): boolean path
如果 path
是绝对路径,则返回 true
,否则返回 false
。
Unix 上的结果很简单
> path.posix.isAbsolute('/home/john')true
> path.posix.isAbsolute('john')false
在 Windows 上,“绝对”不一定意味着“完全限定”(只有第一个路径是完全限定的)
> path.win32.isAbsolute('C:\\Users\\jane')true
> path.win32.isAbsolute('\\Users\\jane')true
> path.win32.isAbsolute('C:jane')false
> path.win32.isAbsolute('jane')false
path.format()
:从部分创建路径type PathObject = {
: string,
dir: string,
root: string,
base: string,
name: string,
ext;
}.format(pathObject: PathObject): string path
从路径对象创建路径
> path.format({dir: '/home/jane', base: 'file.txt'})'/home/jane/file.txt'
我们可以使用 path.format()
更改路径的扩展名
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,
;
})
}
.equal(
assertchangeFilenameExtension('/tmp/file.md', '.html'),
'/tmp/file.html'
;
).equal(
assertchangeFilenameExtension('/tmp/file', '.html'),
'/tmp/file.html'
;
).equal(
assertchangeFilenameExtension('/tmp/file/', '.html'),
'/tmp/file.html'
; )
如果我们知道原始文件名扩展名,我们也可以使用正则表达式来更改文件名扩展名
> '/tmp/file.md'.replace(/\.md$/i, '.html')'/tmp/file.html'
> '/tmp/file.MD'.replace(/\.md$/i, '.html')'/tmp/file.html'
有时我们想在不同的平台上使用相同的路径。然后我们面临两个问题
例如,考虑一个在数据目录上运行的 Node.js 应用程序。假设该应用程序可以使用两种路径进行配置
由于上述问题
我们不能在平台之间重复使用完全限定的路径。
我们可以重复使用指向数据目录的路径。此类路径可以存储在配置文件中(在数据目录内或不在数据目录内)以及应用程序代码中的常量中。为此
下一小节将解释如何实现这两点。
独立于平台的相对路径可以存储为路径段数组,并按如下方式转换为完全限定的平台特定路径
const universalRelativePath = ['static', 'img', 'logo.jpg'];
const dataDirUnix = '/home/john/data-dir';
.equal(
assert.posix.resolve(dataDirUnix, ...universalRelativePath),
path'/home/john/data-dir/static/img/logo.jpg'
;
)
const dataDirWindows = 'C:\\Users\\jane\\data-dir';
.equal(
assert.win32.resolve(dataDirWindows, ...universalRelativePath),
path'C:\\Users\\jane\\data-dir\\static\\img\\logo.jpg'
; )
要创建独立于平台的相对路径,我们可以使用
const dataDir = '/home/john/data-dir';
const pathInDataDir = '/home/john/data-dir/static/img/logo.jpg';
.equal(
assert.relative(dataDir, pathInDataDir),
path'static/img/logo.jpg'
; )
以下函数将独立于平台的相对路径转换为平台特定路径
import * as path from 'node:path';
function splitRelativePathIntoSegments(relPath) {
if (path.isAbsolute(relPath)) {
throw new Error('Path isn’t relative: ' + relPath);
}= path.normalize(relPath);
relPath const result = [];
while (true) {
const base = path.basename(relPath);
if (base.length === 0) break;
.unshift(base);
resultconst dir = path.dirname(relPath);
if (dir === '.') break;
= dir;
relPath
}return result;
}
在 Unix 上使用 splitRelativePathIntoSegments()
> splitRelativePathIntoSegments('static/img/logo.jpg')[ 'static', 'img', 'logo.jpg' ]
> splitRelativePathIntoSegments('file.txt')[ 'file.txt' ]
在 Windows 上使用 splitRelativePathIntoSegments()
> 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' ]
npm 模块 'minimatch'
允许我们将路径与称为*glob 表达式*、*glob 模式*或*glob*的模式进行匹配
import minimatch from 'minimatch';
.equal(
assertminimatch('/dir/sub/file.txt', '/dir/sub/*.txt'), true
;
).equal(
assertminimatch('/dir/sub/file.txt', '/**/file.txt'), true
; )
glob 的用例
更多 glob 库
minimatch 的整个 API 都记录在 项目的自述文件中。在本小节中,我们将介绍最重要的功能。
Minimatch 将 glob 编译为 JavaScript RegExp
对象,并使用它们进行匹配。
minimatch()
:编译和匹配一次minimatch(path: string, glob: string, options?: MinimatchOptions): boolean
如果 glob
与 path
匹配,则返回 true
,否则返回 false
。
两个有趣的选项
.dot: boolean
(默认值:false
)
如果为 true
,则通配符(如 *
和 **
)将匹配“不可见”的路径段(其名称以点开头)
> 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
(默认值:false
)
如果为 true
,则将不带斜杠的模式与路径的基名进行匹配
> minimatch('/dir/file.txt', 'file.txt')false
> minimatch('/dir/file.txt', 'file.txt', {matchBase: true})true
new minimatch.Minimatch()
:编译一次,匹配多次类 minimatch.Minimatch
使我们能够只将 glob 编译为正则表达式一次,并匹配多次
new Minimatch(pattern: string, options?: MinimatchOptions)
以下是此类的使用方法
import minimatch from 'minimatch';
const {Minimatch} = minimatch;
const glob = new Minimatch('/dir/sub/*.txt');
.equal(
assert.match('/dir/sub/file.txt'), true
glob;
).equal(
assert.match('/dir/sub/notes.txt'), true
glob; )
本小节涵盖了语法的要点。但还有更多功能。这些功能记录在此处
即使在 Windows 上,glob 段也由斜杠分隔 - 但它们匹配反斜杠和斜杠(它们是 Windows 上合法的路径分隔符)
> minimatch('dir\\sub/file.txt', 'dir/sub/file.txt')true
Minimatch 不会为我们规范化路径
> minimatch('./file.txt', './file.txt')true
> minimatch('./file.txt', 'file.txt')false
> minimatch('file.txt', './file.txt')false
因此,如果我们不自己创建路径,则必须规范化路径
> path.normalize('./file.txt')'file.txt'
不带*通配符*(更灵活地匹配)的模式必须完全匹配。特别是路径分隔符必须对齐
> 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')false
也就是说,我们必须决定使用绝对路径还是相对路径。
使用选项 .matchBase
,我们可以将不带斜杠的模式与路径的基名进行匹配
> minimatch('/dir/file.txt', 'file.txt', {matchBase: true})true
*
) 匹配任何(部分)单个段*通配符*星号 (*
) 匹配任何路径段或段的任何部分
> 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')true
星号不匹配名称以点开头的“不可见文件”。如果我们要匹配这些文件,则必须在星号前加一个点
> minimatch('file.txt', '*')true
> minimatch('.gitignore', '*')false
> minimatch('.gitignore', '.*')true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt')false
选项 .dot
允许我们关闭此行为
> minimatch('.gitignore', '*', {dot: true})true
> minimatch('/tmp/.log/events.txt', '/tmp/*/events.txt', {dot: true})true
**
) 匹配零个或多个段´**/
匹配零个或多个段
> minimatch('/file.txt', '/**/file.txt')true
> minimatch('/dir/file.txt', '/**/file.txt')true
> minimatch('/dir/sub/file.txt', '/**/file.txt')true
如果我们要匹配相对路径,则模式仍然不能以路径分隔符开头
> minimatch('file.txt', '/**/file.txt')false
双星号不匹配名称以点开头的“不可见”路径段
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json')false
我们可以通过选项 .dot
关闭该行为
> minimatch('/usr/local/.tmp/data.json', '/usr/**/data.json', {dot: true})true
如果我们以感叹号开头一个 glob,则如果感叹号后的模式不匹配,则匹配
> minimatch('file.txt', '!**/*.txt')false
> minimatch('file.js', '!**/*.txt')true
大括号内以逗号分隔的模式,如果其中一个模式匹配,则匹配
> minimatch('file.txt', 'file.{txt,js}')true
> minimatch('file.js', 'file.{txt,js}')true
由双点分隔的一对整数定义了一个整数范围,如果其任何元素匹配,则匹配
> 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')false
也支持用零填充
> 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')true
file:
URL 引用文件在 Node.js 中,有两种常用方法来引用文件
file:
的 URL
实例例如
.equal(
assert.readFileSync(
fs'/tmp/data.txt', {encoding: 'utf-8'}),
'Content'
;
).equal(
assert.readFileSync(
fsnew URL('file:///tmp/data.txt'), {encoding: 'utf-8'}),
'Content'
; )
URL
在本节中,我们将仔细研究类 URL
。有关此类的更多信息
在本章中,我们通过全局变量访问类 URL
,因为这是它在其他 Web 平台上的使用方式。但它也可以被导入
import {URL} from 'node:url';
URL 是 URI 的一个子集。RFC 3986 是 URI 的标准,它区分了 两种*URI 引用*
URL
的构造函数类 URL
可以通过两种方式实例化
new URL(uri: string)
uri
必须是 URI。它指定了新实例的 URI。
new URL(uriRef: string, baseUri: string)
baseUri
必须是 URI。如果 uriRef
是相对引用,则相对于 baseUri
解析它,结果将成为新实例的 URI。
如果 uriRef
是 URI,则它将完全替换 baseUri
作为实例所基于的数据。
在这里,我们可以看到该类的实际应用
// If there is only one argument, it must be a proper URI
.equal(
assertnew URL('https://example.com/public/page.html').toString(),
'https://example.com/public/page.html'
;
).throws(
assert=> new URL('../book/toc.html'),
() /^TypeError \[ERR_INVALID_URL\]: Invalid URL$/
;
)
// Resolve a relative reference against a base URI
.equal(
assertnew URL(
'../book/toc.html',
'https://example.com/public/page.html'
.toString(),
)'https://example.com/book/toc.html'
; )
URL
实例的相对引用让我们重新审视 URL
构造函数的这种变体
new URL(uriRef: string, baseUri: string)
参数 baseUri
被强制转换为字符串。因此,可以使用任何对象 - 只要它在强制转换为字符串时成为有效的 URL 即可
const obj = { toString() {return 'https://example.com'} };
.equal(
assertnew URL('index.html', obj).href,
'https://example.com/index.html'
; )
这使我们能够解析相对于 URL
实例的相对引用
const url = new URL('https://example.com/dir/file1.html');
.equal(
assertnew URL('../file2.html', url).href,
'https://example.com/file2.html'
; )
以这种方式使用时,构造函数与 path.resolve()
大致相似。
URL
实例的属性URL
的实例具有以下属性
type URL = {
: string,
protocol: string,
username: string,
password: string,
hostname: string,
port: string,
hostreadonly origin: string,
: string,
pathname
: string,
searchreadonly searchParams: URLSearchParams,
: string,
hash
: string,
hreftoString(): string,
toJSON(): string,
}
我们可以通过三种常用方式将 URL 转换为字符串
const url = new URL('https://example.com/about.html');
.equal(
assert.toString(),
url'https://example.com/about.html'
;
).equal(
assert.href,
url'https://example.com/about.html'
;
).equal(
assert.toJSON(),
url'https://example.com/about.html'
; )
方法 .toJSON()
使我们能够在 JSON 数据中使用 URL
const jsonStr = JSON.stringify({
pageUrl: new URL('https://exploring.javascript.ac.cn')
;
}).equal(
assert, '{"pageUrl":"https://exploring.javascript.ac.cn"}'
jsonStr; )
URL
属性URL
实例的属性不是自己的数据属性,它们是通过 getter 和 setter 实现的。在下一个示例中,我们使用实用函数 pickProps()
(其代码显示在末尾)将这些 getter 返回的值复制到一个普通对象中
const props = pickProps(
new URL('https://jane:[email protected]:80/news.html?date=today#misc'),
'protocol', 'username', 'password', 'hostname', 'port', 'host',
'origin', 'pathname', 'search', 'hash', 'href'
;
).deepEqual(
assert,
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:[email protected]:80/news.html?date=today#misc'
};
)function pickProps(input, ...keys) {
const output = {};
for (const key of keys) {
= input[key];
output[key]
}return output;
}
唉,路径名是一个单一的原子单元。也就是说,我们不能使用类 URL
来访问它的各个部分(base、扩展名等)。
我们还可以通过设置 .hostname
等属性来更改 URL 的各个部分
const url = new URL('https://example.com');
.hostname = '2ality.com';
url.equal(
assert.href, 'https://2ality.com/'
url; )
我们可以使用 setter 从各个部分创建 URL(Haroen Viaene 的想法)
// Object.assign() invokes setters when transferring property values
const urlFromParts = (parts) => Object.assign(
new URL('https://example.com'), // minimal dummy URL
// assigned to the dummy
parts ;
)
const url = urlFromParts({
protocol: 'https:',
hostname: '2ality.com',
pathname: '/p/about.html',
;
}).equal(
assert.href, 'https://2ality.com/p/about.html'
url; )
.searchParams
管理搜索参数我们可以使用属性 .searchParams
来管理 URL 的搜索参数。它的值是 URLSearchParams
的一个实例。
我们可以使用它来读取搜索参数
const url = new URL('https://example.com/?topic=js');
.equal(
assert.searchParams.get('topic'), 'js'
url;
).equal(
assert.searchParams.has('topic'), true
url; )
我们也可以通过它来更改搜索参数
.searchParams.append('page', '5');
url.equal(
assert.href, 'https://example.com/?topic=js&page=5'
url;
)
.searchParams.set('topic', 'css');
url.equal(
assert.href, 'https://example.com/?topic=css&page=5'
url; )
手动转换文件路径和 URL 很有诱惑力。例如,我们可以尝试通过 myUrl.pathname
将 URL
实例 myUrl
转换为文件路径。然而,这并不总是有效 - 最好使用 此函数
.fileURLToPath(url: URL | string): string url
以下代码将该函数的结果与 .pathname
的值进行比较
import * as url from 'node:url';
//::::: Unix :::::
const url1 = new URL('file:///tmp/with%20space.txt');
.equal(
assert.pathname, '/tmp/with%20space.txt');
url1.equal(
assert.fileURLToPath(url1), '/tmp/with space.txt');
url
const url2 = new URL('file:///home/thor/Mj%C3%B6lnir.txt');
.equal(
assert.pathname, '/home/thor/Mj%C3%B6lnir.txt');
url2.equal(
assert.fileURLToPath(url2), '/home/thor/Mjölnir.txt');
url
//::::: Windows :::::
const url3 = new URL('file:///C:/dir/');
.equal(
assert.pathname, '/C:/dir/');
url3.equal(
assert.fileURLToPath(url3), 'C:\\dir\\'); url
此函数 是 url.fileURLToPath()
的反函数
.pathToFileURL(path: string): URL url
它将 path
转换为文件 URL
> url.pathToFileURL('/home/john/Work Files').href'file:///home/john/Work%20Files'
URL 的一个重要用例是访问作为当前模块同级目录的文件
function readData() {
const url = new URL('data.txt', import.meta.url);
return fs.readFileSync(url, {encoding: 'UTF-8'});
}
此函数使用 import.meta.url
,其中包含当前模块的 URL(在 Node.js 上通常是 file:
URL)。
使用 fetch()
会使前面的代码更具跨平台性。但是,截至 Node.js 18.9.0,fetch()
还不适用于 file:
URL
> await fetch('file:///tmp/file.txt')TypeError: fetch failed
cause: Error: not implemented... yet...
ESM 模块可以通过两种方式使用
如果我们希望一个模块以这两种方式使用,我们需要一种方法来检查当前模块是否是主模块,因为只有这样我们才能执行脚本功能。在本章中,我们将学习如何执行该检查。
使用 CommonJS,我们可以使用以下模式来检测当前模块是否是入口点(来源:Node.js 文档)
if (require.main === module) {
// Main CommonJS module
}
到目前为止,ESM 模块还没有简单的内置方法来检查模块是否是主模块。相反,我们必须使用以下解决方法(基于 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
} }
说明
import.meta.url
包含当前执行的 ESM 模块的 URL。
如果我们确定我们的代码始终在本地运行(这在未来可能会变得不那么常见),我们可以省略 A 行中的检查。如果我们这样做并且代码不在本地运行,至少我们会得到一个异常(而不是静默失败) - 由于 url.fileURLToPath()
(参见下一项)。
我们使用 url.fileURLToPath()
将 URL 转换为本地路径。如果协议不是 file:
,则此函数会抛出异常。
process.argv[1]
包含初始模块的路径。B 行中的比较有效,因为此值始终是绝对路径 - Node.js 按如下方式设置它(源代码)
process.argv[1] = path.resolve(process.argv[1]);
file:
URL当 shell 脚本接收对文件的引用或导出对文件的引用时(例如,通过在屏幕上记录它们),它们实际上始终是路径。但是,在两种情况下我们需要 URL(如前面小节中所述)