>这篇文章是 [Working with file system paths on Node.js](https://2ality.com/2022/07/nodejs-path.html) 学习过程中的个人总结和笔记,大部分内容实际来自于原文章,如果你对 Nodejs 中的文件系统 (path) 感兴趣,我更建议你直接去看原文章。 >在下文中 path 会被翻译成 路径 ## Nodejs 中路径相关(Path-related)的功能 - 大多数路径相关的功能都在模块 `node:path` 中 - 全局变量 `process` 拥有一些方法可以改变当前工作目录(current working directory) - `node:os` 模块拥有一些函数可以返回重要的目录路径 关于 `path` 的使用有三种方法,它们分辨是 - 直接使用 `path` - 使用平台特定版本(platform-specific versions)相关的 `path`: - `path.posix` 对应 Unix 系系统,包括 MacOS - `path.win32` 对应 Windows 系统 而直接使用 `path` 本身,它本身始终支持当前平台,内部似乎做了一些判断,可以从下面 Nodejs REPL 的例子中看出来: ```javascript 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()`](https://nodejs.org/api/process.html#processcwd) 会返回当前 CWD - [`process.chdir(dirPath)`](https://nodejs.org/api/process.html#processchdirdirectory) 改变当前 CWD 至 `dirPath` - 在 `dirPath` 必须有一个路径 - 改变并不会影响 shell,只影响当前运行的 Nodejs 进程 当路径不完整(isn’t fully qualified)的时候,Nodejs 会使用 CWD 来填补缺失的部分 ### 在 Windows 中的当前工作目录(CWD) 在 Windows 系统中,CWD 的工作会有些许不同: - 每个驱动器都有一个 CWD - 有一个当前驱动器(current drive)存在 我们可以使用 `path.chdir()` 同时修改两者: ```javascript process.chdir('C:\\Windows'); process.chdir('Z:\\tmp'); ``` 但是当我们重新访问一个驱动器的时候,Nodejs 会记住当前驱动器的前一个当前目录: ```javascript 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` 解析路径,规则会比较复杂,[详情](https://2ality.com/2022/07/nodejs-path.html#fully-and-partially-qualified-paths-on-windows)可以看原文章,这里就不贴出来了 ## 通过 `node:os` 模块获取重要目录的路径 模块 "node:os "为我们提供了两个重要目录的路径 - [`os.homedir()`](https://nodejs.org/api/os.html#oshomedir) 返回当前用户的主目录路径 - [`os.tmpdir()`](https://nodejs.org/api/os.html#ostmpdir) 返回当前操作系统的临时文件目录路径 ## 路径拼接(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] 是完整路径,那么返回的结果也是完整路径,如果是不完整路径,那么结果也是不完整路径 会有一些特别的结果——当第一个参数之后参数为绝对路径时,那么它会被解析成相对路径: ```javascript path.join('dir', '/tmp') // 'dir/tmp' ``` ## 确保路径是规范化(normalized)的,完整的,或相对的 ### `path.normalize()`:用于将路径规范化 对于不同的系统环境,该函数的行为也有所不同: **在 Unix 系统中**: - 移除单点(.)或双点(..)的路径段 - 将多个路径分隔符变成一个路径分隔符 ```javascript path.normalize('/home/./john/lib/../photos///pet') // '/home/john/photos/pet' ``` **在 Windows 系统中**: - 移除单点(.)或双点(..)的路径段 - 将斜杠转换为反斜杠 - 多个反斜杠转换为一个反斜杠 ```javascript path.win32.normalize('C:\\Users/jane\\doc\\..\\proj\\\\src'), // 'C:\\Users\\jane\\proj\\src' ``` **注意**:只有一个参数的 `path.join` 也会进行路径规范化转换,它的行为和 `path.normalize` 是相同的 ### `path.resolve()` 单参数时的作用 在前面我们已经遇到过 `path.resolve()` 了,用于路径拼接,但是当它只有一个参数的时候它的作用会发生一些变化:它会既将路径规范化又确保返回完整路径 ```javascript path.resolve('/home/./john/lib/../photos///pet') // '/home/john/photos/pet' ``` ### `path.relative()` 创建相对路径 ```javascript path.relative(sourcePath: string, destinationPath: string): string ``` 它会返回一个路径,让我们从 `sourcePath` 到 `destinationPath` ```javascript path.relative('/home/john/', '/home/john/proj/my-lib/README.md') // 'proj/my-lib/README.md' ``` 在 Windows 上,如果 sourcePath 和 destinationPath 在不同的驱动器上,我们会得到一个完整的路径,如下 ```javascript path.relative('Z:\\tmp\\', 'C:\\Users\\Jane\\') // 'C:\\Users\\Jane' ``` 这个函数对于相对路径也是适用的: ```javascript path.relative('proj/my-lib/', 'doc/zsh.txt') // '../../doc/zsh.txt' ``` ### `path.isAbsolute()` 判断是否是一个绝对路径 如果路径是一个绝对路径返回 `true`,反之则返回 `false` ```javascript path.isAbsolute('/home/john') // true ``` 注意:在 Windows 系统中,绝对路径bi'bu'yi'wei'z ## 解析路径:提取路径中的各个部分(文件拓展名等) ### `path.parse()` 创建一个含有路径部分的对象(object with path parts) 提取路径的各个部分,并在一个具有以下属性的对象中返回: - `base`: 路径的最后一段 - `ext`: 路径中文件拓展名 - `name`: `base` 中不包含拓展名的部分 - `root`: 路径的开始 - `dir`: `base` 的所在路径(不包含`base`) ### `path.basename()` 提取路径中的 `base` 部分 ```javascript path.basename('/home/jane/file.txt') // 'file.txt' ``` 此外,它的第二个参数允许删除一些拓展名: **注意:第二个参数是区分大小的** ```javascript path.basename('/home/jane/file.txt', 'xt') // 'file.t' ``` ### `path.dirname()` 提取路径中 `dir` 的部分 ```javascript path.dirname('C:\\Users\\john\\dir\\') // 'C:\\Users\\john' ``` ### `path.extname()` 提取路径中 `ext` 的部分 ```javascript path.extname('/home/jane/file.txt') // '.txt' ```