node之path模块

路径概念介绍

概念

所谓路径,就是定位一个文件所在的位置时,所必须经过 的目录的层级结构的集合.

绝对路径与相对路径

  • windows 下的相对与绝对路径

    资料原文

    完全限定路径(绝对路径)与相对路径

    对于操作文件的 Windows API 函数,文件名通常可以相对于当前目录,而一些 API 需要一个完全限定的路径。如果文件名不是用下列内容之一开头的,则该文件名是相对路径(相对于当前工作目录):

    • 任何格式的通用命名约定(UNC)名称: 总是以两个反斜杠字符("\")开头

    • 带冒号与反斜杠的磁盘指示符,例如“C:\”或“d:\”

    • 一个反斜杠,例如,“\directory”或“\file.txt”,这也属于绝对路径

    如果文件名仅以磁盘指示符开头,后面不带冒号加反斜杠的组合,则它将被解释为该磁盘驱动器上当前目录的相对路径。
    注意,当前目录可能是根目录,也可能不是根目录,这取决于最近在该磁盘上进行“更改目录(cd)”操作时将其设置为什么。这种格式的例子如下:

    • “C: tmp.txt”指的是一个在驱动器 C 上的当前目录中,名为“tmp”的文件
    • “C: tempdir \ tmp.txt”指的是 C 驱动器上当前目录的子目录中的一个文件

    代码示例:(DOS)

    cd "C:\windows"
    
    node C:test.js ::执行C:\windows\test.js
    node D:test.js ::执行D:\test.js
    

    如果一个路径包含“双点”,亦即,路径的一个组件是两个连续的点,它也被称为相对路径;。这个特殊的说明符用于表示当前目录之上的目录,也称为“父目录”。这种格式的例子如下:

    • “. . \ tmp.txt"指定一个名为 tmp.txt 的文件,该文件位于当前目录的父目录中。

    • “. . \ . . \ tmp.txt“指定当前目录之上两个目录的文件。

    • “. . \ tempdir \ tmp.txt"指定一个名为 tmp.txt 的文件,它位于一个名为 tempdir 的目录中,该目录是当前目录的对等目录

    相对路径可以组合这两种示例类型使用,例如“C:..\tmp.txt” , 这是有效的,因为,尽管系统持续跟踪记录当前驱动器的当前目录 . 它还跟踪所有驱动器(如果你的系统有不止一个)中的当前目录, 不管当前驱动器是被设置为哪一个.

    DOS 举例:

    cd /d "D:\test1\test2\test3" ::系统追踪记录D盘及D盘当前目录为D:\test1\test2\test3
    cd /d "C:\windows\Boot" ::系统追踪记录C盘及C盘当前目录为C:\windows\Boot
    cd /d "E:\video" ::系统追踪记录E盘及E盘当前目录为E:\video
    
    node D:..\test.js ::执行D:\test1\test2\test.js
    node C:..\test.js ::执行C:\windows\test.js
    ::注意-所谓追踪和记录多个盘的当前目录,指的是在同一个cmd会话之中记录,多个cmd会话之间是不共享的
    

Windows 与 POSIX 的对比

  • 什么是 POSIX

    可移植操作系统接口(Portable Operating System Interface,缩写为 POSIX):

    1. 是 IEEE 为要在各种 UNIX 操作系统上运行软件,而定义 API 的一系列互相关联的标准的总称,其正式称呼为 IEEE Std 1003,而国际标准名称为 ISO/IEC 9945

    2. 它基本上是 Portable Operating System Interface(可移植操作系统接口)的缩写,而 X 则表明其对 Unix API 的传承

    3. Linux 基本上逐步实现了 POSIX 兼容,但并没有参加正式的 POSIX 认证。

    简单来看,POSIX 就是 UNIX 与类 UNIX 系统.

  • POSIX 与 windows 上路径的区别

    • 路径分隔符

      windows 下使用的是“\”作为分隔符,而 linux 则反其道而行之使用"/"作为分隔符.所以在 windows 环境中获取路径常见 C:\windows\system 的形式,而 linux 常见 /user/share 的形式

    • 绝对路径

      • windows: 以盘符开始,如 C:\a.txt
      • linux: 以根目录/开始, 如/user/share
  • 更多参考

path 模块对 windows 与 POSIX 路径的处理方案

  • windows 路径的注意事项

    因为 windows 使用反斜杠\作为路径分隔符, 而 js 中的反斜杠被用来对特殊字符进行转义. 因此,直接的 windows 文件路径字符串是不能直接在 node 程序中使用的.举例如下:

    let path = require("path");
    let raw = “C:\Windows\node\”;
    
    console.log(path.basename(rawPath));
    // 输出:
    // Windows
    // ode
    // 原因: \node 中的 \n 被解析为一个换行符
    

不过不需要担心,我们只需要在 js 源代码中注意不要这么草率地直接使用这种字面量即可.像是从另一个文件读取内容这种操作,在读取过程中,node 会自动对其中的反斜杠进行转义,不需要我们来操心.如下例:

raw.txt:

C:\Windows\node

test.js

let path = require("path");
let fs = require("fs");

let raw = fs.readFileSync("./raw.txt", "utf-8"); //文件读取时,会将C:\Windows\node自动转义为C:\\Windows\\node
console.log(path.basename(raw)); //node
  • path 模块对 windows 与 POSIX 的解决方案

    path 提供了 path.win32 与 path.posix 两个属性来分别解决 windows 与 posix 类型的路径.

    let path = require("path");
    
    console.log("path:\n");
    console.log(Object.keys(path).length);
    console.log(Object.keys(path));
    
    console.log("path.win32:\n");
    console.log(Object.keys(path.win32).length);
    console.log(Object.keys(path.win32));
    
    console.log("path.posix:\n");
    console.log(Object.keys(path.posix).length);
    console.log(Object.keys(path.posix));
    
    // 输出:
    // path:
    // 16
    // [
    //   'resolve',    'normalize',
    //   'isAbsolute', 'join',
    //   'relative',   'toNamespacedPath',
    //   'dirname',    'basename',
    //   'extname',    'format',
    //   'parse',      'sep',
    //   'delimiter',  'win32',
    //   'posix',      '_makeLong'
    // ]
    // path.win32:
    // 16
    // [
    //   'resolve',    'normalize',
    //   'isAbsolute', 'join',
    //   'relative',   'toNamespacedPath',
    //   'dirname',    'basename',
    //   'extname',    'format',
    //   'parse',      'sep',
    //   'delimiter',  'win32',
    //   'posix',      '_makeLong'
    // ]
    // path.posix:
    // 16
    // [
    //   'resolve',    'normalize',
    //   'isAbsolute', 'join',
    //   'relative',   'toNamespacedPath',
    //   'dirname',    'basename',
    //   'extname',    'format',
    //   'parse',      'sep',
    //   'delimiter',  'win32',
    //   'posix',      '_makeLong'
    // ]
    

    三个对象都拥有完全一样的属性与方法.path 中的主要方法都有对应的 win32 和 posix 版本来分别处理两种不同的路径风格.

路径组件获取

说明: 路径组件的获取函数(方法),都只是基于最基本的字符匹配模式的操作,不会对...进行解析和运算.

path.basename

语法

path.basename(path[, ext])

  • 返回 path 的最后一部分

  • 尾部的分隔符会被忽略

  • 传入可选参数 ext(扩展名),可在结果中过滤掉扩展名

    • ext 参数区分大小写(.html 与.HTML 为不同扩展名)

    • 这与 windows 无视文件扩展名大小写的行为不同.

    • 其实质可以视为:

      path.basename(path).replace(ext,"")

    path.basename("/目录1/目录2/文件.html");
    // 返回: '文件.html'
    path.basename("/目录1/目录2/文件.html", ".html");
    // 返回: '文件'
    path.basename("/目录1/目录2/文件.HTML", ".html");
    // 返回: '文件.HTML'
    

path.dirname

语法

path.dirname(path)

  • 返回 path 的目录名
  • 尾部的目录分隔符(path.sep)会被忽略
path.dirname('/目录1/目录2/目录3');
// 返回: '/目录1/目录2'

说明: 路径组件的获取函数(方法),都只是基于最基本的字符匹配模式的操作,不会对...进行解析和运算.
举例如下:

let path = require("path");

let p1 = "c:\\windows\\node\\test\\..\\";
console.log(path.basename(p1)); //输出 ..

let p2 = "c:\\windows\\node\\..\\test\\test.md";
console.log(path.dirname(p2)); //输出 c:\windows\node\..\test

path.delimiter

这是一个属性值,返回平台特定的路径定界符

  • windows: 分号;
  • posix: 冒号:

path.extname

这是一个属性值,返回路径中的扩展名

扩展名:

  • path 的最后一部分中从最后一次出现 . (句点)字符直到字符串结束
  • path 最后一部分中没有.,或者 path 的基本名称(basename)除了第一个字符以外没有.,则返回空字符

path.sep

这是一个属性值,平台特定的路径片段(组件)分隔符

  • Windows: 反斜杠\
  • POSIX: 正斜杠/

路径处理函数

说明: 路径处理函数,不同于路径组件的获取函数,会对路径中的...进行解析,也就是基于相对路径语法进行路径运算.

path 模块的内容:

  • 错误处理: ERR_INVALID_ARG_TYPE
  • 关键字符的 unicode 码点常量: require 自 internal/constants
  • 字符串类型验证: validateString
  • 工具函数
    • 路径分隔符判断: isPathSeparato
    • POSIX 分隔符判断: isPosixPathSeparator
    • windows 盘符判断: isWindowsDeviceRoot
    • 相对路径解析(解析.或..): normalizeString
    • path 对象格式化为字符串: _format
  • windows 平台系列处理函数封装: win32 对象
  • POSIX 平台系列处理函数封装: posix 对象

主要函数的调用关系

flowchart TD resolve --调用--> normalizeString normalize --调用--> normalizeString join --调用--> normalize relative --调用--> resolve toNameSpacedPath --调用--> resolve

win32 与 posix 对象

  • 各自包含一整套特定于对应平台的路径获取与路径处理函数
  • 各自包含指向对方的引用

示例图:

模块的导出语句

module.exports = process.platform === "win32" ? win32 : posix;

可见, 如果是windows平台,require("path")得到的就是基于win32对象; posix平台得到的自然是posix对象.

我们可以对路径处理函数再进行分类.

绝对路径系列

绝对路径验证-path.isAbsolute(path)

绝对路径验证是基于前面 绝对与相对路径 的内容

windows 下:

path.isAbsolute("//server"); // true -- UNC名称
path.isAbsolute("\\\\server"); // true -- UNC名称
path.isAbsolute("C:/foo/.."); // true -- 带冒号与反斜杠的磁盘指示符
path.isAbsolute("C:\\foo\\.."); // true -- 带冒号与反斜杠的磁盘指示符
path.isAbsolute("bar\\baz"); // false
path.isAbsolute("bar/baz"); // false
path.isAbsolute("."); // false

POSIX 下:

path.isAbsolute("/foo/bar"); // true -- 从根目录开始的路径
path.isAbsolute("/baz/.."); // true -- 从根目录开始的路径
path.isAbsolute("qux/"); // false
path.isAbsolute("."); // false

解析绝对路径-path.resolve([...paths])

  • 将路径或路径片段的序列解析为绝对路径
  • 给定的路径序列会从右到左进行处理,后面的每个 path 会被追加到前面,直到构造出绝对路径
  • 注意: 构造出绝对路径即返回,所以不一定会处理完所有参数
  • 处理完所有给定的 path 片段之后还未生成绝对路径,则会使用当前工作目录

代码示例:(笔者平台为win7)

let path = require("path");
let s1 = "\\test1",
    s2 = "/test2",
    s3 = "test3";

console.log(path.resolve(s3,s2,s1)); // D:\test1
console.log(path.resolve(s3,s2)); // D:\test2
console.log(path.resolve(s3)); // D:\project-mindmap\CATCH_UP\node-path\test3

console.log(path.posix.resolve(s3, s2, s1)); // /test2/\test1
console.log(path.posix.resolve(s3, s2)); // /test2
console.log(path.posix.resolve(s3)); // D:\project-mindmap\CATCH_UP\node-path/test3

分析:

  1. 注意: 输出字符串是没有转义这个概念的,\就是反斜杠,而不是转义的标志

  2. 笔者平台为windows,所以path.resolve是针对windows平台的win32版本的函数

  3. 对s1与s2的判定:

    1. windows下: 均属于绝对路径,所以前两个调用仅仅处理了最右侧的参数

      注: windows平台下, 对UNC及正斜杠开头的绝对路径调用resolve函数,总是将当前目录对应的 盘符:作为根路径

    2. posix下: 仅仅只有s2被判定为绝对路径

      注: 在windows平台调用path.posix.resolve

      1. 并不会对代表当前工作目录的字符串进行转换(反斜杠转正斜杠)
      2. 反斜杠被视为普通的字符

计算相对路径差-path.relative(from, to)

  • 根据调用关系: relative调用resolve函数

    • 先比较from和to的原始字符串,如果相同,返回空串(表示当前目录)
    • 接着调用resolve将from和to处理为绝对路径字符串
      • windows平台下调用win32.resolve
      • posix平台下调用posix.resolve
    • 接着比较两者并计算出相对路径
  • 因为调用resolve,所以又会有resolve包含的特性与问题

计算命名空间化路径名-path.toNamespacedPath(path)

  • 仅在 Windows 系统上,返回给定 path 的等效 名称空间前缀路径

  • 仅在 Windows 系统上有意义

    在 POSIX 系统上,该方法不可操作,并且始终返回 path 而不进行修改

  • windows命名空间(见附录资料)

  • 代码简介:

    • 字符串检查(path非字符串则报错)

    • 调用win32.resolve将path处理为绝对路径形式resolvedPath

    • 判断path形式

      • path本身就为命名空间格式(以"\\\\.""\\\\?"打头)

        return path;
        
      • path为UNC形式:

        return `\\\\?\\UNC\\${resolvedPath.slice(2)}`;
        
      • path为windows盘符打头的形式:

        return `\\\\?\\${resolvedPath}`;
        

路径规范化系列

相对路径解析(模块内部函数)-normalizeString

语法: normalizeString(path, allowAboveRoot, separator, isPathSeparator)

原理分析

  • 函数内部变量及其含义:

  • 函数逐个字符读取path字符串,并在适当的时候进行操作(res回退或者res追加)

    • res回退操作:

      res = res.slice(0, lastSlashIndex);
      

      以上图为例子,经回退操作后,res为project\\mindmap

    • res追加操作:

      res += `${separator}${path.slice(lastSlash + 1, i)}`;
      //其中: separator为特定于平台的分隔符; i为当前字符在path中的下标值
      
  • 函数以变量dots的取值为中心,可以用如下的状态图描述:

stateDiagram-v2 0-->1: 点 1-->2: 点 2-->other: 点 other-->other: 点 0-->&#45&#49: 非分隔符,非点 1-->&#45&#49: 非分隔符,非点 2-->&#45&#49: 非分隔符,非点 other-->&#45&#49: 非分隔符,非点 &#45&#49-->&#45&#49: 非分隔符,非点 0-->0:分隔符\n(res路径追加) 1-->0: 分隔符 2-->0: 分隔符\n(res路径回退) other-->0:分隔符\n(res路径追加) &#45&#49-->0: 分隔符\n(res路径追加) note left of 0 初始状态 end note

状态图分析:

  1. 说明:

    1. 状态图中读取的字符是否属于分隔符,其判断依据是函数的参数isPathSeparator,它是一个函数

    2. normalizeString函数的 消费者 是posix.normalize与win32.normalize

      1. posix.normalize中,是这么调用的:

        path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator);
        
      2. win32.normalize中是这么调用的:

        normalizeString(
                    path.slice(rootEnd),
                    !isAbsolute,
                    "\\",
                    isPathSeparator
                  )
        

      可见,:

      • posix.normalize仅仅将反斜杠视为分隔符, 正斜杠视为普通字符
      • win32.normalize将正反斜杠都视为分隔符
  2. 初始状态为0(dots=0)

  3. 连续点的匹配:

    1. 0状态下,如果读取的下一个字符是点,进入状态1(dots=1)
    2. 1状态下,如果读取的下一个字符是点,进入状态2(dots=2)
    3. 2状态下,如果读取的下一个字符是点,进入状态other(dots=3,4,5....)
    4. other状态下,如果如果读取的下一个字符是点,仍旧是other状态(dots++)
  4. 任何状态,如果读取的下一个字符是分隔符,回到状态0

    1. 状态2回到状态0的同时,执行 res回退操作
    2. 状态other,-1,0回到状态0的同时,执行 res追加操作
  5. 任何状态,如果读取的下一个字符是非分隔符且非点号,回到状态-1(dots=-1)

可以匹配的模式分析:

  1. 因为初始状态是0,因此可以匹配两种类型

    1. 点号打头的模式: .分隔符,..分隔符

    2. 非点号打头的模式: 分隔符.分隔符,分隔符..分隔符

      (分隔符由isPathSeparator参数判断)

    函数也分别对应两种类型,分成了两种情况处理

规范化路径串-path.normalize

语法: path.normalize(path)

功能: 规范化给定的 path ,解析 '..' 和 '.' 片段(调用了内置函数normalizeString)

特点:

  • 多个连续重复的路径段分隔字符(例如 POSIX 上的 / 、Windows 上的 \ 或 / ),替换为单个平台特定的路径段分隔符(POSIX 上的 / 、Windows 上的 \ )
  • 尾部的分隔符会保留
  • 零长度的字符串,则返回 .,表示当前工作目录

代码示例

path.win32.normalize('C:////temp\\\\/\\/\\/foo/bar');
// 返回: 'C:\\temp\\foo\\bar'

路径组合-path.join

语法: path.join([...paths])

功能:

  • 将所有给定的 path 片段连接到一起(使用平台特定的分隔符作为定界符)
  • 规范化生成的路径(调用normalize函数), 意味着可以进行路径的"相对运算"

代码示例:

path.join('/目录1', '目录2', '目录3/目录4', '目录5', '..');
// 返回: '/目录1/目录2/目录3/目录4'

path.join('目录1', {}, '目录2');
// 抛出 'TypeError: Path must be a string. Received {}'

路径对象与字符串互转

路径字符串转对象-path.parse

语法: path.parse(path),其中path是路径字符串

功能:

  • 解析路径字符串
  • 返回一个对象,对象各个属性代表路径的各种组成成分
  • 未调用normalize或join,不会执行路径中的"相对运算"

代码示例:

let path = require("path");
let str = "C:\\test\\test2\\..\\tmp\\tmp.txt";

console.log(path.parse(str));
// 输出:
// {
//   root: 'C:\\',
//   dir: 'C:\\test\\test2\\..\\tmp',
//   base: 'tmp.txt',
//   ext: '.txt',
//   name: 'tmp'
// }

路径对象转字符串-path.format

语法: path.format(pathObject)

功能:

  • 提取参数pathObject中的各个代表路径组成部分的属性
  • 将它们组合成为一个路径字符串
  • path.format的功能相反

注意事项:

当构建 pathObject 时,注意以下属性组合,其中一些属性优先于另一些属性:

  • 如果提供了 pathObject.dir ,则忽略 pathObject.root
  • 如果 pathObject.base 存在,则忽略 pathObject.ext 和 pathObject.name

附录:windows命名空间

资料原文

Windows api中使用的命名空间主要有两类,NT命名空间和Win32命名空间。

NT命名空间被设计为其他子系统和命名空间可以存在的最低级别命名空间,包括Win32子系统和扩展的Win32命名空间。

POSIX是Windows中构建在NT命名空间之上的子系统的另一个例子。

Windows的早期版本还为某些特殊设备定义了几个预定义或保留名称,如通信(串行和并行)端口和默认的显示控制台,两者现在属于被称为NT设备命名空间的一部分,并且在Windows的当前版本中作为向后兼容方案仍然被支持。

win32文件命名空间

本节和下一节总结了Win32名称空间前缀和约定,并描述了它们的使用方法。请注意,这些示例用于Windows API函数,并不一定都适用于Windows shell应用程序,如Windows资源管理器。由于这个原因,win32命名空间比Windows shell应用具有更广泛的使用手段,利用这一点的Windows应用程序可以使用这些命名空间进行开发。

对于文件I/O, \\?\路径字符串的前缀告诉Windows api禁用所有字符串解析,并将在这之后的字符串直接发送到文件系统。例如,如果文件系统支持大路径和文件名,那么你可以超出被Windows api强制定义的MAX_PATH的限制。有关常规最大路径限制的更多信息,请参阅前一节的最大路径长度限制.

因为它关闭了路径字符串的自动展开, \\?\前缀还允许在路径名中使用".."和".",如果您试图使用这些保留的相对路径说明符作为完全限定路径的一部分对文件执行操作,这可能很有用。

许多但不是所有的文件I/O api支持 \\?\前缀; 您应该查看每个API的具体说明加以确定。

请注意,应该使用Unicode api来确保 \\?\前缀允许您超过MAX_PATH的限制.

win32设备命名空间

\\.\前缀将访问Win32设备命名空间,而不是Win32文件命名空间。这就是直接访问物理磁盘和卷的方式,不需要通过文件系统(如果API支持这种类型的访问)。您可以通过这种方式访问除磁盘之外的许多设备(例如,通过使用CreateFileDefineDosDevice函数)。

例如,如果你想要打开系统的串行通信端口1,你可以在CreateFile函数的调用中使用“COM1”。这是因为COM1-COM9是NT名称空间中保留名称的一部分,不过使用\\.\前缀也适用于这些设备名称。相比之下,如果您安装了一个100端口的串行扩展板,并且想要打开COM56,但是你不能使用“COM56”打开它,因为COM56没有预定义的NT命名空间。您需要使用\\.\COM56来打开它. 因为\\.\直接转到设备命名空间,而不是试图定位一个预定义的别名

使用Win32设备命名空间的另一个例子是将CreateFile函数和\\.\PhysicalDiskX(其中X是有效的整数值)或 \\.\CdRomX搭配使用。这允许您绕过文件系统,直接访问这些设备。这是可行的,因为这些设备名是系统在枚举这些设备时创建的,一些驱动程序还会在系统中创建其他别名。例如,实现名称“C:\”的设备驱动程序有自己的命名空间,这个命名空间恰好也是文件系统。

使用CreateFile函数的api通常也可以使用\\.\前缀,因为CreateFile是用来打开文件以及设备的函数,这取决于您使用的参数。

如果你正在使用Windows API函数,你应该只使用\\.\前缀访问设备而不将之用于访问文件。

大多数api不支持\\.\;只有那些设计为使用设备名称空间的设备才能识别它。使用API前,请务必检查每个API的详细说明来加以确定。

NT命名空间

也有一些api允许使用NT命名空间,但是Windows对象管理器在大多数情况下不需要这样做。使用Windows Sysinternals WinObj工具在系统对象浏览器中浏览Windows名称空间可以很好地帮助说明这一点。当您运行这个工具时,您看到的是以root\开头的NT命名空间。名为Global??的子文件夹是Win32命名空间所在的地方。已命名的设备对象驻留在NT命名空间中的Device子目录的。在这里,您还可以找到Serial0Serial1,如果存在的话,它们是代表最初的两个COM端口的设备对象。表示卷的设备对象类似于HarddiskVolume1,尽管数字后缀可能会有所变化。Harddisk0子目录下的名称DR0是一个表示磁盘的设备对象的例子,等等。

为了让这些设备对象可以被Windows应用程序访问,设备驱动程序在Win32命名空间中创建一个符号链接(symlink)Global??到它们各自的设备对象。例如,Global??子目录下的COM0COM1是到Serial0Serial1的符号链接,C: 是到HarddiskVolume1的符号链接,Physicaldrive0是到DR0的符号链接,等等。
如果没有符号链接,特定的设备“Xxx”对于任何使用Win32命名空间的Windows应用程序都是不可用的,如前所述。但是,可以使用任何支持NT命名空间的api,并用\device\Xxx的绝对路径格式打开该设备的句柄。

随着通过终端服务和虚拟机增加了多用户支持,有必要进一步在Win32名称空间内虚拟化系统范围的根设备。这是通过将名为GLOBALROOT的符号链接添加到Win32命名空间来实现的,您可以在前面讨论过的WinObj浏览器工具的Global??子目录中看到它,并且可以通过路径\\?\GLOBALROOT访问它。其中,\\?\前缀确保它后面的路径在系统对象管理器的真正根路径中查找,而不是与会话相关的路径。

winObj的使用

  • 下载

  • 使用

    • 下载并解压

    • 以管理员权限运行

posted @ 2020-12-24 16:40  peterzhangsnail  阅读(708)  评论(0编辑  收藏  举报