1
0
Fork 0
Obsidian 管理的个人笔记仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

11 KiB

这篇文章是 Working with file system paths on Node.js 学习过程中的个人总结和笔记,大部分内容实际来自于原文章,如果你对 Nodejs 中的文件系统 (path) 感兴趣,我更建议你直接去看原文章。

在下文中 path 会被翻译成 路径

  • 大多数路径相关的功能都在模块 node:path
  • 全局变量 process 拥有一些方法可以改变当前工作目录(current working directory)
  • node:os 模块拥有一些函数可以返回重要的目录路径

关于 path 的使用有三种方法,它们分辨是

  • 直接使用 path
  • 使用平台特定版本(platform-specific versions)相关的 path
    • path.posix 对应 Unix 系系统,包括 MacOS
    • path.win32 对应 Windows 系统

而直接使用 path 本身,它本身始终支持当前平台,内部似乎做了一些判断,可以从下面 Nodejs REPL 的例子中看出来:

path.parse === path.posix.parse    // true

不同的 path 特定版本的处理逻辑也会有不同,如果没有使用正确的版本,可能会出现预期之外的结果

基本路径概念及其API支持

当前工作目录 current working directory(CWD)

  • 如果我们使用一个包含相对路径的命令(command),该路径将针对 CWD 进行解析
  • 如果我们在省略了一个路径的期望路径(就是不传),那么这时候会使用 CWD
  • 在 UNIX 和 Windows 中切换 CWD 的命令都是 cd

process 是 Nodejs 提供的全局变量,它为我们提供了一些用于获取(getting)或设置(setting)CWD 的方法:

  • process.cwd() 会返回当前 CWD
  • process.chdir(dirPath) 改变当前 CWD 至 dirPath
    • dirPath 必须有一个路径
    • 改变并不会影响 shell,只影响当前运行的 Nodejs 进程

当路径不完整(isn’t fully qualified)的时候,Nodejs 会使用 CWD 来填补缺失的部分

在 Windows 中的当前工作目录(CWD)

在 Windows 系统中,CWD 的工作会有些许不同:

  • 每个驱动器都有一个 CWD
  • 有一个当前驱动器(current drive)存在

我们可以使用 path.chdir() 同时修改两者:

process.chdir('C:\\Windows');
process.chdir('Z:\\tmp');

但是当我们重新访问一个驱动器的时候,Nodejs 会记住当前驱动器的前一个当前目录:

process.cwd() // 'Z:\\tmp'

process.chdir('C:');
process.cwd() // 'C:\\Windows'

完整路径和不完整路径以及路径解析

  • 一个完整路径(fully qualified path)不依赖于任何信息,可以原样使用
  • 一个不完整的路径(partially qualified path)是缺少信息的,在使用之前我们需要将其解变成一个完整路径

Unix

Unix 只知道两种路径:

  • 绝对路径(Absolute paths):是一个完整路径,总是以斜杠开头:
    • /home/user/video
  • 相对路径(Relative paths):是一个不完整的路径,会以文件名或是点(dot)开头:
    • dir
    • ../dir

使用 path.resolve() 在 Unix 中解析路径非常的简单,其返回会是一个绝对路径

Windows

在 Windows 中有四种路径:

  • 绝对路径和相对路径
  • 以上两种路径都可以带卷标和不带卷标

带卷标(drive letter)的绝对路径才是一个完整路径,而其他都是不完整路径,在 Windows 中使用 path.resolve 解析路径,规则会比较复杂,详情可以看原文章,这里就不贴出来了

通过 node:os 模块获取重要目录的路径

模块 "node:os "为我们提供了两个重要目录的路径

  • os.homedir() 返回当前用户的主目录路径
  • os.tmpdir() 返回当前操作系统的临时文件目录路径

路径拼接(concatenating paths)

有两个方法用于路径拼接:

  • path.resolve() 总是返回一个完整路径
  • path.join() 则会保留相对路径

path.resolve():拼接并返回一个完整路径

可以把 path.resolve 的行为总结为以下的几点:

  • 从当前工作目录(CWD)开始拼接
  • 将 path[0] (参数0)与之前的结果进行比对
  • 将 path[1] (参数1)与之前的结果进行比对
  • 对所有剩余的路径参数做同样的处理
  • 返回完整的路径

在上边我们已经知晓了它的行为,下面这些结果也印证了上面的总结

  • 当参数为空时,会返回当前的工作目录路径(CWD)
    • > path.resolve() // result: "/usr/local"
  • 当有一个或多个相对路径时,将会从当前工作路径(CWD)开始拼接
    • > path.resolve('./bin', 'sub') // result: "/usr/local/bin/sub"
  • 当参数中有完整路径时,始终会覆盖前一个拼接结果
    • > path.resolve('bin', '/home') // result: "/home"

path.join():拼接并保持相对路径

path.join()path.resolve() 不同,它从 path[0] 开始拼接,保留了不完整的路径,如果 path[0] 是完整路径,那么返回的结果也是完整路径,如果是不完整路径,那么结果也是不完整路径

会有一些特别的结果——当第一个参数之后参数为绝对路径时,那么它会被解析成相对路径:

path.join('dir', '/tmp') // 'dir/tmp'

确保路径是规范化(normalized)的,完整的,或相对的

path.normalize():用于将路径规范化

对于不同的系统环境,该函数的行为也有所不同:

在 Unix 系统中

  • 移除单点(.)或双点(..)的路径段
  • 将多个路径分隔符变成一个路径分隔符
path.normalize('/home/./john/lib/../photos///pet')
// '/home/john/photos/pet'

在 Windows 系统中

  • 移除单点(.)或双点(..)的路径段
  • 将斜杠转换为反斜杠
  • 多个反斜杠转换为一个反斜杠
path.win32.normalize('C:\\Users/jane\\doc\\..\\proj\\\\src'),
// 'C:\\Users\\jane\\proj\\src'

注意:只有一个参数的 path.join 也会进行路径规范化转换,它的行为和 path.normalize 是相同的

path.resolve() 单参数时的作用

在前面我们已经遇到过 path.resolve() 了,用于路径拼接,但是当它只有一个参数的时候它的作用会发生一些变化:它会既将路径规范化又确保返回完整路径

path.resolve('/home/./john/lib/../photos///pet')
// '/home/john/photos/pet'

path.relative() 创建相对路径

path.relative(sourcePath: string, destinationPath: string): string

它会返回一个路径,让我们从 sourcePathdestinationPath

path.relative('/home/john/', '/home/john/proj/my-lib/README.md')
// 'proj/my-lib/README.md'

在 Windows 上,如果 sourcePath 和 destinationPath 在不同的驱动器上,我们会得到一个完整的路径,如下

path.relative('Z:\\tmp\\', 'C:\\Users\\Jane\\')
// 'C:\\Users\\Jane'

这个函数对于相对路径也是适用的:

path.relative('proj/my-lib/', 'doc/zsh.txt')
// '../../doc/zsh.txt'

path.isAbsolute() 判断是否是一个绝对路径

如果路径是一个绝对路径返回 true,反之则返回 false

path.isAbsolute('/home/john') // true

注意:在 Windows 系统中,绝对路径比不意味着就是一个完整路径,如下

path.isAbsolute('C:\\Users\\jane') // true 只有这个是完整路径
path.isAbsolute('\\Users\\jane') // true

解析路径:提取路径中的各个部分(文件拓展名等)

path.parse() 创建一个含有路径部分的对象(object with path parts)

提取路径的各个部分,并在一个具有以下属性的对象中返回:

  • base: 路径的最后一段
    • ext: 路径中文件拓展名
    • namebase 中不包含拓展名的部分
  • root: 路径的开始
  • dirbase 的所在路径(不包含base

path.basename() 提取路径中的 base 部分

path.basename('/home/jane/file.txt')
// 'file.txt'

此外,它的第二个参数允许删除一些拓展名: 注意:第二个参数是区分大小的

path.basename('/home/jane/file.txt', 'xt')
// 'file.t'

path.dirname() 提取路径中 dir 的部分

path.dirname('C:\\Users\\john\\dir\\')
// 'C:\\Users\\john'

path.extname() 提取路径中 ext 的部分

path.extname('/home/jane/file.txt')
// '.txt'

path.format() 从路径对象中创建路径

path.parse() 的作用相反,path.format() 用于从路径对象中生成对应的路径并返回

例子:改变文件拓展名

const 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 重写 name 和 ext
		base: undefined, 
		ext: newExtension,
	});
};

在不同的平台使用相同的路径

有些时候,我们希望能在不同的平台使用相同的路径,这时候我们会面临两个问题:

  1. 路径分隔符可能会不同
  2. 文件结构可能不同:主目录和临时目录路径不同

举一个例子,考虑一个 Nodejs 应用,它在一个有数据的目录上操作,假设它可以配置两种路径:

  • 完整路径
  • 数据目录内的路径(相对的路径)

由于开头所说的问题:

  • 我们并不能使用复用相同的完整目录在不同的平台
  • 我们可以复用数据目录内的路径,这样的路径可以存储在配置文件或是代码的常量中,要做到这一点我们需要:
    • 必须使用相对路径
    • 跨平台复用时确保路径分隔符的正确

与平台无关的相对路径(Relative platform-independent paths)

与平台无关的相对路径可以存储在一个数组中,可以使用以下的方法转换为对应平台的路径

const universalRelativePath = ['static', 'img', 'logo.jpg'];
const dataDirUnix = '/home/john/data-dir';
path.posix.resolve(dataDirUnix, ...universalRelativePath),
// '/home/john/data-dir/static/img/logo.jpg'

以下代码用于将路径转换为数组

const 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;
};