路徑概念介紹
概念
所謂路徑,就是定位一個檔案所在的位置時,所必須經過 的目錄的層級結構的集合.
絕對路徑與相對路徑
-
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 的對比
-
可移植作業系統介面(Portable Operating System Interface,縮寫為 POSIX):
-
是 IEEE 為要在各種 UNIX 作業系統上執行軟體,而定義 API 的一系列互相關聯的標準的總稱,其正式稱呼為 IEEE Std 1003,而國際標準名稱為 ISO/IEC 9945
-
它基本上是 Portable Operating System Interface(可移植作業系統介面)的縮寫,而 X 則表明其對 Unix API 的傳承
-
Linux 基本上逐步實現了 POSIX 相容,但並沒有參加正式的 POSIX 認證。
簡單來看,POSIX 就是 UNIX 與類 UNIX 系統.
-
-
POSIX 與 windows 上路徑的區別
-
路徑分隔符
windows 下使用的是“\”作為分隔符,而 linux 則反其道而行之使用"/"作為分隔符.所以在 windows 環境中獲取路徑常見
C:\windows\system
的形式,而 linux 常見/user/share
的形式 -
絕對路徑
- windows: 以碟符開始,如
C:\a.txt
- linux: 以根目錄
/
開始, 如/user/share
- windows: 以碟符開始,如
-
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 物件
主要函式的呼叫關係
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
分析:
-
注意: 輸出字串是沒有轉義這個概念的,
\
就是反斜槓,而不是轉義的標誌 -
筆者平臺為windows,所以path.resolve是針對windows平臺的win32版本的函式
-
對s1與s2的判定:
-
windows下: 均屬於絕對路徑,所以前兩個呼叫僅僅處理了最右側的引數
注: windows平臺下, 對UNC及正斜槓開頭的絕對路徑呼叫resolve函式,總是將當前目錄對應的
碟符:
作為根路徑 -
posix下: 僅僅只有s2被判定為絕對路徑
注: 在windows平臺呼叫
path.posix.resolve
- 並不會對代表當前工作目錄的字串進行轉換(反斜槓轉正斜槓)
- 反斜槓被視為普通的字元
-
計算相對路徑差-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的取值為中心,可以用如下的狀態圖描述:
狀態圖分析:
-
說明:
-
狀態圖中讀取的字元是否屬於分隔符,其判斷依據是函式的引數
isPathSeparator
,它是一個函式 -
normalizeString函式的
消費者
是posix.normalize與win32.normalize-
posix.normalize中,是這麼呼叫的:
path = normalizeString(path, !isAbsolute, "/", isPosixPathSeparator);
-
win32.normalize中是這麼呼叫的:
normalizeString( path.slice(rootEnd), !isAbsolute, "\\", isPathSeparator )
可見,:
- posix.normalize僅僅將反斜槓視為分隔符, 正斜槓視為普通字元
- win32.normalize將正反斜槓都視為分隔符
-
-
-
初始狀態為0(dots=0)
-
連續點的匹配:
- 0狀態下,如果讀取的下一個字元是點,進入狀態1(dots=1)
- 1狀態下,如果讀取的下一個字元是點,進入狀態2(dots=2)
- 0狀態下,如果讀取的下一個字元是點,進入狀態other(dots=3,4,5....)
- other狀態下,如果如果讀取的下一個字元是點,仍舊是other狀態(dots++)
-
任何狀態,如果讀取的下一個字元是分隔符,回到狀態0
- 狀態2回到狀態0的同時,執行 res回退操作
- 狀態other回到狀態0的同時,執行 res追加操作
-
任何狀態,如果讀取的下一個字元是非分隔符且非點號,回到狀態-1(dots=-1)
可以匹配的模式分析:
-
因為初始狀態是0,因此可以匹配兩種型別
-
點號打頭的模式:
.分隔符
,..分隔符
-
非點號打頭的模式:
分隔符.分隔符
,分隔符..分隔符
(分隔符由
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支援這種型別的訪問)。您可以通過這種方式訪問除磁碟之外的許多裝置(例如,通過使用CreateFile和DefineDosDevice函式)。
例如,如果你想要開啟系統的序列通訊埠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
子目錄的。在這裡,您還可以找到Serial0
和Serial1
,如果存在的話,它們是代表最初的兩個COM埠的裝置物件。表示卷的裝置物件類似於HarddiskVolume1
,儘管數字字尾可能會有所變化。Harddisk0
子目錄下的名稱DR0
是一個表示磁碟的裝置物件的例子,等等。
為了讓這些裝置物件可以被Windows應用程式訪問,裝置驅動程式在Win32名稱空間中建立一個符號連結(symlink)Global??
到它們各自的裝置物件。例如,Global??
子目錄下的COM0
和COM1
是到Serial0
和Serial1
的符號連結,C:
是到HarddiskVolume1
的符號連結,Physicaldrive0
是到DR0的符號連結,等等。
如果沒有符號連結,特定的裝置“Xxx”對於任何使用Win32名稱空間的Windows應用程式都是不可用的,如前所述。但是,可以使用任何支援NT名稱空間的api,並用\device\Xxx
的絕對路徑格式開啟該裝置的控制程式碼。
隨著通過終端服務和虛擬機器增加了多使用者支援,有必要進一步在Win32名稱空間內虛擬化系統範圍的根裝置。這是通過將名為GLOBALROOT
的符號連結新增到Win32名稱空間來實現的,您可以在前面討論過的WinObj瀏覽器工具的Global??
子目錄中看到它,並且可以通過路徑\\?\GLOBALROOT
訪問它。其中,\\?\
字首確保它後面的路徑在系統物件管理器的真正根路徑中查詢,而不是與會話相關的路徑。
winObj的使用
-
使用
-
下載並解壓
-
以管理員許可權執行
-