傻傻分不清楚的連結 fs.symlink、 ln、ln -s(詳解與應用)
前言
寫這篇文章的原因
- 最近在做一些工程化相關的內容,有使用到
fs.symlink(target,path)
, 檢視Node.js
文件發現講的一般!所以這裡詳細整理下,並且記得之前在Linux
下直接使用命令建立軟鏈通過ls -s source_file target_file
在想這兩個引數位置有些怎麼不一致呢? - 本文是我上一篇工程化系列文章中的一個小插曲,上篇文章現代前端工程化-徹底搞懂基於 Monorepo 的 lerna 模組(從原理到實戰) 有提到軟連結,那篇沒有對軟硬連結沒有進行詳細講解,只是簡單介紹了
fs.symlink
中軟連結建立的基本使用,本文進行詳細展開下。 - 軟連線的建立應用場景還是比較廣泛,比如
npm link
leran
內部模組互相引用等等。
inode
在講解軟/硬連結之前,先了解一個linux
系統中重要的概念inode
。眾所周知,檔案儲存在硬碟上,硬碟的最小儲存單位叫做 "扇區"(Sector
,每個扇區儲存512
位元組).作業系統讀取硬碟的時候,不會一個個扇區地讀取,因為這樣效率太低,而是一次性連續讀取多個扇區,這種一次性讀取的連續多個扇區就是"塊"(block
)。這種由多個扇區組成的"塊",是檔案存取的最小單位。"塊"的大小,最常見的是4KB
,即連續八個sector
組成一個block
。
檔案資料都儲存在"塊"
中,那麼很顯然,我們還必須找到一個地方儲存檔案的元資訊,比如檔案的建立者、檔案的建立日期、檔案的大小等等。這種儲存檔案元資訊的區域就叫做inode
"索引節點"
。
注意:我們開啟一個一個檔案時,系統首先找到檔名對應的
inode
號碼,然後通過inode
號碼獲取inode
資訊,然後根據inode
資訊中的檔案資料所在block
讀出資料。
上述概念的文章內容比較多,不太容易記憶,看圖!
inode 中包括了哪些內容 ?
inode包含檔案的元資訊,具體來說有以下內容:
- 檔案的位元組數
- 檔案擁有者的
User ID
- 檔案的
Group ID
- 檔案的讀、寫、執行許可權
- 檔案的時間戳,共有三個:
ctime
指inode
上一次變動的時間,mtime
指檔案內容上一次變動的時間,atime
指檔案上一次開啟的時間。 - 連結數,即有多少檔名指向這個
inode
- 檔案資料
block
的位置
linux 命令如何獲取一個檔案的 inode 資訊
可以直接使用linux
命令stat
檢視某個檔案的inode
資訊
stat example.js
輸出資訊
Node.js 中如何獲取一個檔案的 inode 資訊
在Node.js
中,fs
提供了stat
函式檢視相關資訊
fs.statSync('./example.js');
輸出資訊
檔案資訊 Stats { dev: 16777223, mode: 33188, nlink: 1, uid: 501, gid: 20, rdev: 0, blksize: 4096, ino: 11904170, size: 0, blocks: 0, atimeMs: 1616564768255.48, mtimeMs: 1616564768255.48, ctimeMs: 1616564786778.5532, birthtimeMs: 1616564768255.48, atime: 2021-03-24T05:46:08.255Z, mtime: 2021-03-24T05:46:08.255Z, ctime: 2021-03-24T05:46:26.779Z, birthtime: 2021-03-24T05:46:08.255Z }
每一個inode
都有一個唯一的標識碼 ,上面的輸出資訊中ino
就是inode
的唯一標識碼,在linux
系統內部使用inode
的標識碼來識別檔案,並不使用檔名。之前系的
在linux
系統中,目錄也是一種檔案。目錄檔案包含一系列目錄項,每一個目錄項由兩部分組成:所包含檔案的檔名,以及檔名對應的inode
標識碼。我們可以使用ls -i
來列出目錄中的檔案以及所有的inde
標識碼。這裡也可以解釋可能小夥伴們覺得說不通的問題,僅修改目錄的讀許可權,並不能實現讀取目錄下所有檔案內容的原因,最後需要通過遞迴目錄下的檔案來進行修改。
軟連結與硬連結
什麼是軟連結(soft link,也叫符號連結)
軟連結類似於Window
中的 “快捷方式” 。建立軟連結會建立一個新的inode
,比如為檔案a
建立了軟連結檔案b,檔案b
內部會指向a
的inode
。當我們讀取檔案b的時候,系統會自動導向檔案a
,檔案b
就是檔案a
軟連線(或者叫符號連結)。
- 訪問:建立了軟連結後我們就可以使用不同的檔名訪問相同的內容,
- 修改:修改檔案
a
的內容,檔案b
的內容也會發生改變,對檔案內容的修改向放映到所有檔案。 - 刪除:當我們刪除原始檔
a
時,在訪問軟連線檔案b是,會報錯"No such file or directory"
可以直接使用linux
命令ln -s source target
來建立軟連結(注意:表示target
"指向"source
)
ln -s ./target/a.js b.js
執行shell
命令後,會出現b.js
檔案,軟連結建立成功。
Node.js 中建立軟連結
基礎用法
Node.js
官方文件提供了symlinkSync
函式建立軟連結。
fs.symlinkSync(target,path,type) target <string> | <Buffer> | <URL> // 目標檔案 path <string> | <Buffer> | <URL> // 建立軟鏈對應的地址 type <string>
它會建立名為path
的連結,該連結指向target
。type
引數僅在Windows
上可用,在其他平臺上則會被忽略。它可以被設定為'dir'
、'file'
或'junction'
。如果未設定type
引數,則Node.js
將會自動檢測target
的型別並使用'file'
或'dir'
。如果target
不存在,則將會使用'file'
。Windows
上的連線點要求目標路徑是絕對路徑。當使用'junction'
時,target
引數將會自動地標準化為絕對路徑。
- 使用示例
const res = fs.symlinkSync('./target/a.js','./b.js');
這段程式碼的意思是為 建立一個軟連結b.js
指向了檔案./targert/a.js
,當a.js
中的內容發生變化時,b.js
檔案也會發生相同的改變。
⚠️注意:如果對目錄建立軟連結,方法中第三個引數需要傳'dir'(雖然第三個引數只在windows下生效,這樣傳遞可以確保跨平臺不會出現問題):
fs.symlink(target,path,'dir')
上面講解了linux
和Node.js
建立軟連結的兩種方式,認真看的小夥伴可能發現問題,為什麼Node.js
中fs.symlink(target,path)
和shell
命令中的ln -s source target
兩個引數好像是反的呢,是不是會有這樣的疑問?
其實如果前面的小例子你都嘗試了,會發現後面傳遞的兩個引數順序實際是一致的,都是讓後面新建的的"指向=>"前面的哦!
什麼是硬連結(hard link)
一般情況,一個檔名"唯一"對應一個inode
。但是linux
允許多個檔名都指向同一個inode
。表示我們可以使用不同對檔名訪問同樣的內容;對檔案內容進行修改將放映到所有檔案;刪除一個檔案不影響另一個檔案對訪問。這種機制就被稱為"硬連結"
硬連結的建立
可以直接使用linux
命令ln source target
來建立硬連結(注意:source
已存在的檔案,target
是將要建立的連結)
ln ./target/a.js c.js
執行shell
命令後,會出現c.js
檔案,硬連結建立成功。
與軟連線不同,只能給檔案建立硬連結,不能給目錄建立硬連結。並且source檔案必須存在,否則建立硬連結時會報錯。
刪除一個檔案不會影響另一個檔案的訪問。原因是什麼?實際上,檔案inode
中還有一個連結數的資訊,每多一個檔案指向這個inode
,該數字就會加1
,每少一個檔案指向這個inode
,該數字就會減1
,當值減到 0,系統就自動回收inode
及其對應的block
區域。很像是一種引用計數的垃圾回收機制。
當我們對某個檔案建立了硬連結後,對應的inode
的連結數會是``2(原檔案本身已經有一個指向),當刪除一個檔案時,連結數變成
1`,並沒達到回收的條件,所以我們還是可以訪問檔案。
軟連結與硬連結對比分析總結
-
使用 ln source target 建立硬連結;使用 ln -s source target 建立軟連結
-
硬連結不會建立額外 inode,和原始檔共用同一個 inode;軟連結會建立額外一個檔案(額外 inode),指向原始檔的 inode
-
建立硬連結時,source 必須存在且只能是檔案;建立軟連結時,source 可以不存在而且可以是目錄
-
刪除原始檔不會影響硬連結檔案的訪問(因為 inode 還在);刪除原始檔會影響軟連結檔案的訪問(因為指向的 inode 已經不存在了)
-
對於已經建立的同名連結,不能再次建立,除非刪掉或者使用 -f 引數
應用場景
npm link
npm link
的原理也是通過軟鏈來實現的。當我們想要除錯本地開發的npm
模組包(還沒有釋出或者修改了一些內容)時,需要使用npm link
來進行除錯 舉個例子:
有兩個專案create-mono-repo
和create-mono-repo-testCli
在create-mono-repo
專案節點下執行npm link
成功後 ,在create-mono-repo-testCli
專案目錄下執行npm link create-mono-repo
這樣就可以完成除錯了
lerna
lerna
建立的專案,packages
目錄下各模組互相依賴也是基於fs.symlinkSync
建立軟連結實現的。
具體實現程式碼和地址如下:
原始碼對應地址
https://github.com/lerna/lerna/tree/main/utils/create-symlink
function createSymbolicLink(src, dest, type) { log.silly("createSymbolicLink", [src, dest, type]); return fs .lstat(dest) .then(() => fs.unlink(dest)) .catch(() => { /* nothing exists at destination */ }) .then(() => fs.symlink(src, dest, type)); }
參考文章
- 關於inode的講解 https://www.ruanyifeng.com/blog/2011/12/inode.html
- lerna 原始碼 https://github.com/lerna/lerna
轉自https://mp.weixin.qq.com/s/sd6LSSNbZNh2JhDucdhcIQ