建、刪除及運(yùn)維實(shí)戰(zhàn)指南)
1. 鏈接到底是個(gè)什么東西為什么你總在面試題里見到它剛接觸Linux文件系統(tǒng)那會(huì)兒我對(duì)“鏈接”這個(gè)概念一直模模糊糊。直到有一次部署一個(gè)多版本共存的工具鏈需要讓/usr/local/bin/tool始終指向當(dāng)前激活版本又不想每次升級(jí)都改環(huán)境變量才真正把軟鏈接和硬鏈接的區(qū)別吃透。這篇文章就圍繞“在Linux里創(chuàng)建和刪除鏈接”這件事把背后的原理、實(shí)操命令、踩坑經(jīng)驗(yàn)一次講清楚。不管你是剛學(xué)Linux命令的新手還是天天跟服務(wù)器打交道但一直靠ln -s一把梭的老手都能從里面找到之前沒注意到的細(xì)節(jié)。先把結(jié)論擺出來Linux里的鏈接分兩種軟鏈接symbolic link也叫符號(hào)鏈接和硬鏈接hard link。軟鏈接本質(zhì)上是一個(gè)獨(dú)立的文件里面存的是目標(biāo)路徑的字符串類似Windows的快捷方式硬鏈接則不是獨(dú)立文件它是同一個(gè)inode索引節(jié)點(diǎn)的另一個(gè)目錄項(xiàng)換句話說多個(gè)文件名指向同一份數(shù)據(jù)。創(chuàng)建用ln命令刪除用rm或unlink但兩種鏈接刪除時(shí)的行為差異很大這也是最容易出問題的地方。為什么值得單獨(dú)寫一篇因?yàn)殒溄舆@東西看著簡(jiǎn)單實(shí)際用起來坑不少刪軟鏈接時(shí)手滑加了斜杠結(jié)果把目標(biāo)目錄內(nèi)容刪了硬鏈接以為能跨分區(qū)用結(jié)果報(bào)Invalid cross-device link刪了源文件發(fā)現(xiàn)硬鏈接還在以為刪錯(cuò)了。這些問題背后都是對(duì)inode和目錄項(xiàng)理解不到位。下面我按“設(shè)計(jì)思路—核心細(xì)節(jié)—實(shí)操過程—問題排查”的順序展開盡量把每個(gè)“為什么”都講透。2. 軟鏈接與硬鏈接的設(shè)計(jì)思路拆解2.1 從inode說起文件系統(tǒng)到底怎么存文件要理解鏈接得先理解Linux文件系統(tǒng)的基本模型。在ext4、xfs這類常見文件系統(tǒng)里一個(gè)文件由兩部分組成inode和目錄項(xiàng)dentry。inode存的是元數(shù)據(jù)——文件類型、權(quán)限、大小、時(shí)間戳以及最重要的數(shù)據(jù)塊在磁盤上的位置。目錄項(xiàng)則是目錄里的一條記錄把“文件名”映射到“inode號(hào)”。你可以把inode想象成一本書的正文目錄項(xiàng)就是圖書館目錄卡片上的書名。書名可以有好幾個(gè)但正文只有一份。硬鏈接就是給同一本書多印幾張目錄卡片卡片上的書名不同但都指向同一本書。軟鏈接則是單獨(dú)一張卡片上面寫的不是“第幾本書”而是“請(qǐng)去看某某書名的那本書”——它是一個(gè)指向路徑的指針而不是指向inode。這個(gè)模型解釋了很多現(xiàn)象硬鏈接不能跨文件系統(tǒng)因?yàn)閕node號(hào)只在單個(gè)文件系統(tǒng)內(nèi)唯一硬鏈接不能指向目錄因?yàn)橹赶蚰夸洉?huì)形成環(huán)讓文件系統(tǒng)遍歷陷入死循環(huán)早期系統(tǒng)允許后來為了安全禁止了軟鏈接可以跨文件系統(tǒng)、可以指向目錄因?yàn)樗娴氖锹窂阶址鷌node無(wú)關(guān)。2.2 兩種鏈接的選型邏輯什么時(shí)候用哪個(gè)理解了原理選型就清楚了。我總結(jié)了一張對(duì)照表實(shí)際工作中照著選基本不會(huì)錯(cuò)對(duì)比維度硬鏈接軟鏈接本質(zhì)同一inode的另一個(gè)名字獨(dú)立文件存目標(biāo)路徑跨文件系統(tǒng)不支持支持指向目錄不支持支持源文件刪除后數(shù)據(jù)仍在鏈接可用鏈接變成斷鏈danglinginode號(hào)與源文件相同與源文件不同占用空間幾乎不占額外空間占用一個(gè)inode和少量空間權(quán)限與源文件共享獨(dú)立權(quán)限通常為777典型用途備份、節(jié)省空間的多入口版本切換、路徑簡(jiǎn)化、跨分區(qū)引用選型的核心判斷就兩條需不需要跨分區(qū)源文件會(huì)不會(huì)被刪。需要跨分區(qū)只能用軟鏈接源文件可能被刪且希望鏈接繼續(xù)有效用硬鏈接。日常運(yùn)維里軟鏈接用得更多因?yàn)殪`活硬鏈接多用在備份場(chǎng)景比如給數(shù)據(jù)庫(kù)文件做快照式備份多個(gè)文件名指向同一份數(shù)據(jù)改一個(gè)全都改省空間又保證一致性。2.3 一個(gè)容易被忽略的細(xì)節(jié)軟鏈接的“相對(duì)”與“絕對(duì)”創(chuàng)建軟鏈接時(shí)目標(biāo)路徑寫相對(duì)路徑還是絕對(duì)路徑結(jié)果完全不同。這一點(diǎn)新手極容易踩坑。如果你在/home/user下執(zhí)行l(wèi)n -s target.txt link.txt那么link.txt里存的是字符串target.txt它是相對(duì)于link.txt所在目錄解析的。如果你之后把link.txt移動(dòng)到別的目錄它就會(huì)斷鏈因?yàn)樗€在找同目錄下的target.txt。而用絕對(duì)路徑ln -s /home/user/target.txt link.txt無(wú)論鏈接被移到哪里只要目標(biāo)文件還在原位置鏈接就有效。所以我的經(jīng)驗(yàn)是除非你明確知道鏈接和目標(biāo)永遠(yuǎn)在同一目錄且一起移動(dòng)否則一律用絕對(duì)路徑。這個(gè)習(xí)慣能省掉大量“昨天還好好的今天怎么斷了”的排查時(shí)間。3. 創(chuàng)建鏈接的核心命令與實(shí)操要點(diǎn)3.1 ln命令的完整參數(shù)解析創(chuàng)建鏈接的命令是ln全稱link。它的基本語(yǔ)法是ln [選項(xiàng)] 目標(biāo) 鏈接名不加任何選項(xiàng)時(shí)ln創(chuàng)建的是硬鏈接。要?jiǎng)?chuàng)建軟鏈接必須加-ssymbolic。這是最容易記混的地方——很多人以為ln默認(rèn)就是軟鏈接結(jié)果創(chuàng)建出來的是硬鏈接刪源文件后發(fā)現(xiàn)鏈接還在數(shù)據(jù)也沒丟一臉懵。常用選項(xiàng)我整理如下-s創(chuàng)建軟鏈接最常用沒有之一-f強(qiáng)制覆蓋已存在的鏈接文件腳本里常用-n當(dāng)鏈接名是已存在的軟鏈接指向目錄時(shí)把它當(dāng)普通文件處理而不是跟進(jìn)目錄配合-f用-v顯示創(chuàng)建過程調(diào)試腳本時(shí)有用-r創(chuàng)建相對(duì)路徑的軟鏈接會(huì)自動(dòng)計(jì)算相對(duì)路徑比較智能-t指定鏈接存放的目錄適合批量創(chuàng)建其中-n和-f的組合值得單獨(dú)說。假設(shè)/opt/app/current是一個(gè)指向/opt/app/v1的軟鏈接你想把它重新指向/opt/app/v2。如果直接ln -sf /opt/app/v2 /opt/app/current在某些情況下ln會(huì)跟進(jìn)current指向的目錄在v1里面創(chuàng)建一個(gè)叫v2的鏈接而不是替換current本身。加上-n就能避免這個(gè)問題ln -sfn /opt/app/v2 /opt/app/current這個(gè)-sfn組合是版本切換場(chǎng)景的標(biāo)配建議直接背下來。3.2 硬鏈接創(chuàng)建語(yǔ)法簡(jiǎn)單限制不少創(chuàng)建硬鏈接就是最樸素的lnln /data/report.csv /data/report_backup.csv執(zhí)行后report.csv和report_backup.csv的inode號(hào)相同用ls -li能看到$ ls -li /data/report*.csv 131074 -rw-r--r-- 2 user user 10240 Jan 10 09:00 /data/report.csv 131074 -rw-r--r-- 2 user user 10240 Jan 10 09:00 /data/report_backup.csv注意第二列的數(shù)字2這是硬鏈接計(jì)數(shù)表示這個(gè)inode被幾個(gè)目錄項(xiàng)引用。每創(chuàng)建一個(gè)硬鏈接計(jì)數(shù)加一每刪除一個(gè)計(jì)數(shù)減一減到零時(shí)數(shù)據(jù)塊才真正被釋放。這個(gè)計(jì)數(shù)是理解硬鏈接刪除行為的關(guān)鍵。硬鏈接的限制前面提過不能跨文件系統(tǒng)不能指向目錄。如果你嘗試ln /data/file /mnt/other/file而/data和/mnt/other在不同分區(qū)會(huì)報(bào)ln: failed to create hard link /mnt/other/file /data/file: Invalid cross-device link這不是權(quán)限問題是文件系統(tǒng)層面的硬性限制改權(quán)限也沒用只能改用軟鏈接。3.3 軟鏈接創(chuàng)建路徑寫法的講究軟鏈接創(chuàng)建加-s即可ln -s /data/report.csv /data/report_link.csv用ls -l看會(huì)顯示箭頭指向$ ls -l /data/report_link.csv lrwxrwxrwx 1 user user 16 Jan 10 09:05 /data/report_link.csv - /data/report.csv第一位的l表示這是鏈接文件權(quán)限位顯示rwxrwxrwx但這只是占位實(shí)際權(quán)限由目標(biāo)文件決定。鏈接文件本身的大小是目標(biāo)路徑字符串的長(zhǎng)度這里是16字節(jié)/data/report.csv正好16個(gè)字符。關(guān)于路徑寫法再?gòu)?qiáng)調(diào)一次絕對(duì)路徑優(yōu)先。如果非要用相對(duì)路徑-r選項(xiàng)可以幫你自動(dòng)計(jì)算cd /data ln -sr report.csv report_link.csv-r會(huì)算出report.csv相對(duì)于/data的路徑存進(jìn)鏈接里。這樣即使整個(gè)/data目錄被整體移動(dòng)到別處只要內(nèi)部相對(duì)關(guān)系不變鏈接依然有效。這在制作可移植的目錄包時(shí)很有用。提示創(chuàng)建軟鏈接時(shí)如果目標(biāo)文件還不存在ln -s不會(huì)報(bào)錯(cuò)會(huì)創(chuàng)建一個(gè)斷鏈。這在某些場(chǎng)景下是有意為之比如先建鏈接后放文件但多數(shù)時(shí)候是筆誤建議創(chuàng)建后用ls -l確認(rèn)箭頭指向的目標(biāo)存在。4. 刪除鏈接的完整實(shí)操與行為差異4.1 刪除軟鏈接一個(gè)斜杠引發(fā)的血案刪除軟鏈接用rm或unlink都行rm /data/report_link.csv # 或者 unlink /data/report_link.csvunlink只能刪單個(gè)文件不能加選項(xiàng)適合腳本里精確刪除。rm更靈活但有個(gè)致命陷阱如果你在鏈接名后面加了斜杠rm會(huì)跟進(jìn)鏈接指向的目錄刪除目錄里的內(nèi)容而不是刪除鏈接本身。假設(shè)/opt/app/current是指向/opt/app/v1的軟鏈接你執(zhí)行rm -rf /opt/app/current/這個(gè)尾部斜杠會(huì)讓rm把current解析為目錄然后遞歸刪除v1里的所有內(nèi)容。鏈接current本身還在但指向的目錄被清空了。這個(gè)坑我親眼見過有人把生產(chǎn)環(huán)境的版本目錄刪光就因?yàn)槎啻蛄艘粋€(gè)斜杠。正確做法是rm /opt/app/current不加斜杠rm會(huì)把current當(dāng)普通文件鏈接文件刪除只刪鏈接不動(dòng)目標(biāo)。注意刪除軟鏈接永遠(yuǎn)只刪鏈接本身不會(huì)刪目標(biāo)文件。這是軟鏈接和硬鏈接在刪除行為上最大的區(qū)別之一。如果你刪了軟鏈接發(fā)現(xiàn)目標(biāo)還在別慌這是正常的。4.2 刪除硬鏈接計(jì)數(shù)減一數(shù)據(jù)未必消失刪除硬鏈接同樣是rmrm /data/report_backup.csv但效果和刪軟鏈接完全不同。刪掉一個(gè)硬鏈接后inode的鏈接計(jì)數(shù)從2變成1數(shù)據(jù)塊依然被report.csv引用文件內(nèi)容完好。只有當(dāng)你把所有硬鏈接都刪掉計(jì)數(shù)歸零數(shù)據(jù)塊才會(huì)被釋放。這帶來一個(gè)很實(shí)用的特性硬鏈接可以作為防誤刪的保險(xiǎn)。比如你有一個(gè)重要配置文件創(chuàng)建一個(gè)硬鏈接放在另一個(gè)目錄即使原文件被誤刪硬鏈接還在數(shù)據(jù)不丟。當(dāng)然前提是這兩個(gè)鏈接在同一個(gè)文件系統(tǒng)內(nèi)。反過來如果你修改了硬鏈接指向的內(nèi)容所有鏈接看到的內(nèi)容都會(huì)變因?yàn)樗鼈儽緛砭褪峭环輸?shù)據(jù)。這一點(diǎn)和軟鏈接一樣——軟鏈接修改目標(biāo)所有指向它的鏈接也都能看到變化。區(qū)別只在于硬鏈接是“同一個(gè)文件多個(gè)名字”軟鏈接是“多個(gè)指針指向同一個(gè)路徑”。4.3 批量清理斷鏈的實(shí)用腳本軟鏈接用久了難免產(chǎn)生斷鏈——目標(biāo)文件被刪或移動(dòng)鏈接還在。斷鏈本身無(wú)害但堆積多了影響排查??梢杂胒ind批量找出來find /path/to/search -xtype l-xtype l專門匹配“鏈接指向的目標(biāo)不存在”的情況。確認(rèn)無(wú)誤后批量刪除find /path/to/search -xtype l -delete如果想先看看有哪些再?zèng)Q定刪不刪可以find /path/to/search -xtype l -print這個(gè)技巧在清理長(zhǎng)期運(yùn)行的服務(wù)器時(shí)特別有用尤其是那些頻繁做版本切換的目錄斷鏈能攢下幾十上百個(gè)。5. 常見問題與排查技巧實(shí)錄5.1 創(chuàng)建鏈接報(bào)錯(cuò)速查表實(shí)際操作中遇到的報(bào)錯(cuò)我整理成了一張速查表方便對(duì)照排查報(bào)錯(cuò)信息原因解決方法File exists鏈接名已存在加-f強(qiáng)制覆蓋或先刪除Invalid cross-device link硬鏈接跨文件系統(tǒng)改用軟鏈接-sPermission denied目標(biāo)目錄無(wú)寫權(quán)限檢查目錄權(quán)限或用sudoToo many levels of symbolic links軟鏈接形成環(huán)檢查鏈接指向刪除環(huán)中的某個(gè)鏈接No such file or directory目標(biāo)路徑不存在創(chuàng)建斷鏈確認(rèn)目標(biāo)路徑拼寫或先創(chuàng)建目標(biāo)Operation not permitted嘗試對(duì)目錄創(chuàng)建硬鏈接硬鏈接不支持目錄改用軟鏈接其中“軟鏈接成環(huán)”值得展開說。如果你創(chuàng)建了a - b和b - a訪問a時(shí)會(huì)報(bào)Too many levels of symbolic links。系統(tǒng)在解析路徑時(shí)最多跟進(jìn)40層不同內(nèi)核可能不同超過就報(bào)錯(cuò)。排查方法是ls -l逐個(gè)看箭頭指向找到環(huán)的入口刪掉一個(gè)即可。5.2 硬鏈接計(jì)數(shù)異常怎么查有時(shí)候你會(huì)發(fā)現(xiàn)某個(gè)文件的硬鏈接計(jì)數(shù)莫名其妙很高比如一個(gè)普通文件顯示鏈接數(shù)為5但你只創(chuàng)建了1個(gè)硬鏈接。這通常是因?yàn)橛谐绦蛟诤笈_(tái)創(chuàng)建了硬鏈接或者文件被多個(gè)目錄引用。用find按inode號(hào)反查所有硬鏈接find / -inum 131074 2/dev/null-inum按inode號(hào)查找2/dev/null屏蔽權(quán)限報(bào)錯(cuò)。這樣能列出所有指向同一inode的路徑一目了然。這個(gè)技巧在排查“為什么刪了文件磁盤空間沒釋放”時(shí)特別有用——很可能還有進(jìn)程持有該文件的硬鏈接或文件描述符。5.3 刪除鏈接時(shí)的三個(gè)實(shí)操心得第一刪軟鏈接永遠(yuǎn)不加尾部斜杠。這個(gè)習(xí)慣要刻進(jìn)肌肉記憶尤其是用rm -rf的時(shí)候。我現(xiàn)在的做法是刪鏈接前先ls -l確認(rèn)一下箭頭確認(rèn)是鏈接再刪多花兩秒鐘避免大事故。第二硬鏈接刪除前想清楚數(shù)據(jù)還要不要。因?yàn)橛叉溄庸蚕頂?shù)據(jù)刪一個(gè)不影響其他但如果你以為刪了硬鏈接數(shù)據(jù)就沒了可能會(huì)誤判。反過來如果你以為刪了硬鏈接數(shù)據(jù)還在結(jié)果那是最后一個(gè)鏈接數(shù)據(jù)就真沒了。判斷依據(jù)就是ls -l第二列的鏈接計(jì)數(shù)。第三腳本里刪鏈接用unlink更安全。unlink不支持遞歸也不支持斜杠跟進(jìn)天然避免了誤刪目錄內(nèi)容的風(fēng)險(xiǎn)。在自動(dòng)化腳本里能用unlink就不用rm。5.4 一個(gè)真實(shí)的排查案例之前遇到過一個(gè)問題某服務(wù)日志文件app.log被輪轉(zhuǎn)后磁盤空間沒釋放df顯示分區(qū)快滿了但du統(tǒng)計(jì)目錄大小卻對(duì)不上。排查發(fā)現(xiàn)日志輪轉(zhuǎn)腳本把a(bǔ)pp.log重命名成了app.log.1但服務(wù)進(jìn)程還持有原文件的文件描述符沒有重新打開新日志文件。這時(shí)候app.log.1雖然被刪了或者被移走但進(jìn)程還引用著那個(gè)inode數(shù)據(jù)塊無(wú)法釋放。解決方法是用lsof | grep deleted找到持有已刪除文件的進(jìn)程重啟服務(wù)或讓它重新打開日志。這個(gè)案例說明鏈接和文件描述符是兩個(gè)層面的引用刪鏈接只是減少目錄項(xiàng)引用進(jìn)程持有的文件描述符是另一回事。理解這一點(diǎn)對(duì)排查磁盤空間問題很關(guān)鍵。6. 鏈接在真實(shí)運(yùn)維場(chǎng)景中的典型用法6.1 版本切換軟鏈接的經(jīng)典舞臺(tái)多版本共存的場(chǎng)景軟鏈接幾乎是標(biāo)配。比如部署了一個(gè)應(yīng)用有v1.0、v1.1、v2.0三個(gè)版本目錄對(duì)外統(tǒng)一用current指向當(dāng)前版本ln -sfn /opt/app/v1.1 /opt/app/current升級(jí)時(shí)只需改鏈接指向ln -sfn /opt/app/v2.0 /opt/app/current回滾就是再指回去。整個(gè)過程原子操作服務(wù)幾乎無(wú)感知。配合-sfn三件套不會(huì)出現(xiàn)鏈接套鏈接的混亂。這個(gè)模式在容器化之前是部署的標(biāo)準(zhǔn)做法現(xiàn)在雖然容器普及了但在一些傳統(tǒng)服務(wù)和非容器化環(huán)境里依然廣泛使用。6.2 節(jié)省空間硬鏈接的備份妙用做增量備份時(shí)硬鏈接能大幅節(jié)省空間。比如用rsync的--link-dest選項(xiàng)每次備份只存變化的部分未變化的文件用硬鏈接指向上一次備份rsync -a --link-dest/backup/2024-01-09 /data/ /backup/2024-01-10/這樣2024-01-10目錄里沒變的文件和前一天的是硬鏈接共享數(shù)據(jù)塊變了的文件存新副本。整個(gè)備份目錄看起來是完整的實(shí)際只占增量空間。這個(gè)方案在自建備份系統(tǒng)里非常實(shí)用比全量備份省空間比增量備份恢復(fù)方便。6.3 路徑簡(jiǎn)化讓長(zhǎng)路徑變短有些工具或配置要求固定路徑但實(shí)際文件在很深的目錄里。用軟鏈接做個(gè)短路徑指向ln -s /very/deep/nested/path/to/config /etc/myapp/config這樣配置文件更新時(shí)只需改實(shí)際文件/etc/myapp/config始終有效。類似的還有把數(shù)據(jù)盤掛載點(diǎn)鏈接到應(yīng)用期望的路徑避免改應(yīng)用配置。7. 幾個(gè)容易混淆的邊界問題7.1 軟鏈接的權(quán)限為什么是777ls -l看軟鏈接權(quán)限總是rwxrwxrwx這不是說誰(shuí)都能改它而是因?yàn)檐涙溄拥臋?quán)限位在Linux里不被使用顯示成777只是占位。實(shí)際訪問控制由目標(biāo)文件的權(quán)限決定。你改軟鏈接的權(quán)限chmod也沒用系統(tǒng)會(huì)忽略。這一點(diǎn)和硬鏈接不同硬鏈接共享源文件的inode權(quán)限就是源文件的權(quán)限。7.2 移動(dòng)軟鏈接和移動(dòng)目標(biāo)文件移動(dòng)軟鏈接本身mv link /other/如果鏈接存的是相對(duì)路徑移動(dòng)后可能斷鏈存的是絕對(duì)路徑移動(dòng)后依然有效。移動(dòng)目標(biāo)文件所有指向它的軟鏈接都會(huì)斷因?yàn)槁窂阶兞恕S叉溄觿t不受影響因?yàn)橛叉溄硬灰蕾嚶窂街灰蕾噄node只要inode還在鏈接就有效。7.3 刪除目標(biāo)文件后硬鏈接還在嗎在而且數(shù)據(jù)完好。這正是硬鏈接和軟鏈接的核心區(qū)別。軟鏈接刪目標(biāo)后變成斷鏈硬鏈接刪目標(biāo)其實(shí)是刪了目標(biāo)那個(gè)名字后其他硬鏈接依然指向同一inode數(shù)據(jù)可正常讀寫。只有所有硬鏈接都刪除inode計(jì)數(shù)歸零數(shù)據(jù)才釋放。8. 我個(gè)人的幾條經(jīng)驗(yàn)總結(jié)用鏈接這些年踩過的坑基本都集中在“路徑”和“刪除”兩件事上。路徑方面絕對(duì)路徑優(yōu)先相對(duì)路徑只在明確知道整體移動(dòng)場(chǎng)景時(shí)用刪除方面軟鏈接不加斜杠硬鏈接看計(jì)數(shù)。這兩條記住了90%的問題都能避免。另外ln -sfn這個(gè)組合值得單獨(dú)練熟版本切換、配置更新、路徑重定向都靠它。find -xtype l清理斷鏈find -inum反查硬鏈接這兩個(gè)命令在排查問題時(shí)效率極高。最后理解inode和目錄項(xiàng)的關(guān)系比死記命令參數(shù)重要得多——原理清楚了遇到新問題也能推出來。如果你正在學(xué)Linux建議找個(gè)測(cè)試目錄親手創(chuàng)建軟鏈接和硬鏈接刪源文件、刪鏈接、移動(dòng)文件觀察每種操作后ls -li的輸出變化。親手做一遍比看十篇文章都管用。