第四章 Linux 檔案系統

小昇發表於2015-05-21

不管我們使用什麼作業系統,都離不開對檔案以及目錄的操作。可以說,執行任何計算機程式本質上都是在操作檔案。

想像一下,一個 Linux 系統擁有數十萬個檔案,不僅要考慮如何有效地組織如此多的物件,而且還要考慮到它們可能位於不同型別的儲存裝置之上。Linux 的檔案系統被設計得十分精巧,雖然從表面上看它非常複雜,但其中的每件事物都有它存在的意義。

你可能會問:現在的 Linux 系統大多都提供了圖形化的檔案管理器,為什麼還要使用這些命令呢?
答案是:因為相比於圖形化介面,命令列介面下的檔案操作要高效和靈活許多。隨著遇到的問題變得越來越複雜,你會漸漸意識到命令列程式的強大之處。

本章先簡單地介紹一下 Linux 的檔案系統,然後由淺入深地帶領大家熟悉 Linux 下操作檔案以及目錄的基本命令。

4.1 樹型結構檔案系統

類似於 Windows 系統,Linux 也使用分層目錄結構來組織所有的檔案。在任何一個目錄內,都可以包含檔案以及其他的目錄。檔案系統的第一級目錄稱為“根目錄”,用 / 表示,所有其他的檔案或目錄都包含在其下,並且一層一層組織成樹型結構。

enter image description here
圖 4-1 標準Linux檔案系統

與 Windows 的分割槽概念不同,Linux 始終只有一棵檔案系統樹,但這並不意味著所有的檔案都儲存在同一物理裝置上。事實上,Linux 系統將每一個磁碟分割槽都當作一個單獨的裝置,每個裝置都有自己的檔案系統樹,但是這棵樹最終必須附加到主樹的某一個節點上。

Linux 系統採用這種方式連線小型檔案系統,稱為掛載(mount)。小檔案系統在主樹中附加到的目錄稱為掛載點(mount point)。當斷開檔案系統時,稱為解除安裝(umount)檔案系統。下面舉一個例子:

假如我們像在 Windows 上一樣,將磁碟分為 3 個區:C 盤、D 盤、E 盤(Linux 將每個磁碟分割槽當作一個單獨的裝置)。然後將 C 盤掛載到 / 根目錄位置上,將 D 盤掛載到 /boot 位置上,將 E 盤掛載到 /home 位置上。那麼當我們訪問 /boot 時,實際上訪問的是 D 盤。相應地,home 目錄下的內容實際上是儲存在 E 盤,而根目錄下的其他檔案和目錄都儲存在 C 盤。在檔案系統中,boot 目錄和 home 目錄都在根目錄之下,但實際上它們位於不同的磁碟分割槽。

一般情況下,掛載點是空目錄,不過也可以掛載到非空目錄。比如上面的例子中,如果在 / 根目錄下本來就有 /boot 目錄,那麼將 D 盤掛載到 /boot 目錄後,訪問 /boot 目錄就變成訪問 D 盤,而原來 /boot 目錄下的檔案和目錄就看不到了。當然,原來的 /boot 目錄依然存在,只是無法訪問而已,只要解除安裝掉 D 盤,就又可以看到原來的 /boot 目錄了。


【小提示】
掛載和解除安裝是系統管理任務,需要擁有管理員許可權才能執行。想要顯示當前掛載到系統上的所有檔案系統列表,只需輸入:

$ mount

看到諸如 /dev/sda1 字樣的條目就代表 SATA 硬碟分割槽。
Linux 下硬體掛載後的名稱可以見下表:

enter image description here
圖 4-2 硬體掛載後的名稱

例如,第一塊 SATA 硬碟稱為 /dev/sda,第二塊稱為 /dev/sdb。如果第一塊硬碟還劃分為多個分割槽,那麼第一個分割槽稱為 /dev/sda1,第二個分割槽稱為 /dev/sda2,以此類推。需要注意的是,我們常用的 U 盤,即 USB 快閃記憶體,它被視為一個可移除的 SCSI 磁碟,所以也被命名為像 /dev/sda1 這樣的名稱。

4.4 home 目錄

Linux 的許多目錄中都存放著重要的系統檔案,因而普通使用者並沒有許可權使用這些目錄。為了讓每個使用者都能夠有序地管理自己的檔案和目錄,且不同使用者之間互不干擾,Linux 為每個使用者建立了一個“home”目錄,也稱為使用者主目錄。

這裡需要說明的是,“home”只是這個目錄的稱謂,並不是這個目錄的名稱,真正的使用者主目錄名預設就是使用者名稱,並且所有使用者的“home”目錄都存放在 /home 目錄下。比如當前登陸的使用者標識為 jim,那麼他的“home”目錄就是 /home/jim。

系統預設每個使用者在自己的“home”目錄下擁有完全的許可權,但是並沒有許可權訪問其他使用者的“home”目錄,這就保證了每個使用者的獨立性,實現了使用者之間的互不干擾。

但是有一個特殊使用者的“home”目錄不在 /home 目錄下,那就是 root 超級使用者,這個使用者標識由管理員使用。


【小提示】
在許多系統上,/home 目錄屬於輔助檔案系統,在掛載之前不可用。而管理員必須總是能夠控制系統,也就是說 root 使用者的“home”目錄必須總是可用,所以 Linux 將 /root 目錄安排在根目錄下,因而總是可用的。


使用者每次登入時,環境變數(後面會詳細介紹) Home 被設定為自己“home”目錄的名稱,可以這樣檢視:

$ echo $HOME

就能看到當前使用者“home”目錄的實際位置了。

4.5 工作目錄

如果我們每一次輸入命令訪問檔案都要敲入長長的路徑名,不僅麻煩,而且還容易出錯。比如說我們想要檢視一下 /home/jim/document 這個檔案,需要輸入:

$ cat /home/jim/document

過了一會,又想要檢視 /home.ji/project2/readme.txt 這個檔案,又需要輸入:

$ cat /home/jim/project2/readme.txt

由此可見,每次都要輸入完整的路徑名非常不方便。為了解決這個問題,Linux 將當前在檔案系統樹中的位置稱為工作目錄,當要訪問工作目錄中的檔案或目錄時,只需直接輸入檔名或目錄名即可。同樣使用上面的例子,假如我們已經在 /home/jim 目錄下,那麼上面的命令就等價於:

$ cat document

$ cat project2/readme.txt

可見,在命令中使用工作目錄,可以帶來很大的方便。

Linux 檔案系統就好像一個複雜的迷宮,有時可能會迷失自己所處的位置,這時可以使用 pwd(print working directory)命令來顯示當前工作目錄:

$ pwd 

4.6 絕對路徑名與相對路徑名

與 Windows 系統一樣,Linux 下的路徑名通過列舉由“/”分隔開的目錄序列來描述檔案樹中的一個位置。

如果目錄序列從根目錄開始,則稱之為絕對路徑名(absolute pathname);如果目錄序列從工作目錄開始,則稱之為相對路徑名(relative pathname)。

依然使用前面一章中的例子,如果我們想要檢視 /home/jim/project2 目錄下的 readme.txt 檔案,可以直接輸入:

$ cat /home/jim/project2/readme.txt

這裡使用的就是絕對路徑名,因為它描述了從根目錄一直到 readme.txt 的所有目錄序列。

而如果我們已經切換到 /home/jim,就可以使用相對路徑名:

$ cat project2/readme.txt

其中 project2/readme.txt 是一個相對路徑名,指的是從當前工作目錄 /home/jim 開始,project2 目錄下的 readme.txt 檔案。

總而言之,絕對路徑名與相對路徑名是等價的,你可以根據實際情況使用,哪個方便就使用哪個。

為了更方便地輸入路徑,Linux 提供了 3 種便利的路徑名縮寫。
(1) .. 代表的是父目錄
(2) . 代表的是工作目錄本身
(3) ~ 代表的是使用者的“home”目錄

下面舉一個例子來說明,假設當前工作目錄為 /home/jim/project2,我想要把 /home/jim 目錄下的 document 檔案複製到 /home/jim/project2 目錄下,可以這樣寫:

$ cp ../document .

複製命令 cp 會在後面詳細介紹,這裡 ../document 是要複製的檔案路徑,. 是要複製到的路徑。因為目錄 jim 是 project2 的父目錄,而 document 檔案在目錄 jim 下,所以使用 ..,因為要將 document 複製到 project2 目錄下,而 project2 正是工作目錄本身,所以使用 .。

4.7 cd - 切換工作目錄

為了切換工作目錄,可以使用 cd 命令,語法為:

$ cd [切換到的目錄名稱 | -]

注意:中括號表示裡面的內容可以省略,| 表示並列多個內容,本書以後不再做說明。

如果沒有指定目錄名稱,cd 預設切換到使用者的“home”目錄。

如果用 - 代替目錄名稱,cd 將切換到前一目錄,這樣可以方便地在兩個目錄之間切換。

比如說我想要將工作目錄切換到 /home/jim,可以輸入:

$ cd /home/jim

或者

$ cd

因為 /home/jim 就是當前使用者 jim 的“home”目錄。

這時,又想要切換到 /home/jim/project2,可以輸入:

$ cd project2

這裡使用了相對路徑名。


【小提示】 只要路徑名不以“/”開頭,Linux 就認為是相對路徑名。


假如在 /home/jim 目錄下還有一個 project1 目錄,而我想要切換到它,可以輸入:

$ cd ../project1

這裡使用了路徑名縮寫 ..,因為此時的工作目錄是 /home/jim/project2,所以 .. 代表的是其父目錄 /home/jim。

可以看到,要切換到某一個目錄,可以有很多種寫法。靈活使用路徑名縮寫以及相對路徑名可以大幅提高工作效率。而如果你在錯綜複雜的檔案系統中迷失方向,不要忘記還有指南針 pwd

4.8 ls - 列舉目錄內容

我們大多數精力都花費在思考和操作檔案上,而檔案位於目錄中,所以檢視目錄的工具十分重要。到目前為止,這類工具中最有用的就是 ls。

只要簡單地輸入 ls,就能看到當前工作目錄下包含的檔案和子目錄列表:

$ ls

也可以指定要列出內容的目錄,語法為:

$ ls 目錄…

可以看到,ls 可以列出多個指定目錄的內容。


【進階操作】

$ ls -1

強制以每個檔名佔一行的形式輸出。注意選項是“1”而不是“l”。

$ ls -R

R 是(recursive)遞迴的意思,ls 將列舉所有直接或間接的子目錄和檔案的資訊。

$ ls | wc -l

可以顯示在工作目錄中有多少個檔案,這裡用到了管道以及 wc 程式,後面會詳細介紹。


預設情況下,ls 程式不顯示以“.”開頭的檔名,可以把這類檔案稱為點檔案或隱藏檔案。為了顯示隱藏檔案,可以使用 -a 選項:

$ ls -a

將會顯示目錄下所有的檔案。

當使用 ls 時,有幾個選項可以用來和檔名一起顯示許多資訊,稱為長目錄列表。其中最有用的選項是 -l(long listing)選項:

$ ls -l

可以檢視檔案的詳細資訊,依次為:
(1) 檔案型別
(- 普通檔案;l 符號連結;d 目錄)
(2) 擁有者許可權
(第一位讀許可權,r 有,- 無;第二位寫許可權,w 有,- 無;第三位執行許可權,x 有,- 無)
(3) 擁有者所在使用者組的許可權 (同上)
(4) 其他人的許可權 (同上)
(5) 連結數量
(6) 擁有者的名稱
(7) 擁有者所在使用者組名稱
(8) 檔案大小 (位元組)
(9) 最後修改時間

檔案與目錄的許可權顯示內容相似,但含義不同。對於讀許可權:檔案和目錄相同,代表可以檢視其中的內容。對於寫許可權:檔案代表可以編輯它,目錄則代表可以在其中建立、刪除、重新命名檔案。對於執行許可權:檔案代表可以把它當作程式執行,目錄則代表可以使用 cd 命令切換進這個目錄。

初學者並不需要記住上面所有的內容,一般比較常用的就是檢視一下檔案型別、檔案大小和檔案最後修改時間。

如果就想檢視當前目錄的詳細資訊,可以再加上 -d 選項:

$ ls -ld

【進階操作】 如果希望按時間順序顯示檔案,可以使用 -t 選項:

$ ls -lt

按從最新修改到最舊修改的順序顯示。

如果希望顯示檔案的訪問時間而不是修改時間,使用 -u 選項:

$ ls -lu

訪問時間指的是上一次讀取檔案的時間。

如果想按訪問時間順序顯示檔案,可以輸入:

$ ls -ltu

將按從最新訪問到最舊訪問的順序顯示。

如果想反轉顯示順序,可以新增上 -r 選項,將按照從最舊到最新的順序顯示,例如:

$ ls -lrt

預設情況下,ls 以位元組為單位顯示檔案大小,當檔案非常大時容易混亂,為了以千位元組 (KB) 或兆位元組 (MB) 為單位顯示,可以使用 -h 選項(human-readable):

$ ls -hl

如果想顯示目錄本身的資訊,則可以使用 -d 選項(directory)。例如顯示 /bin 目錄自身的資訊:

$ ls -dl /bin

-d 選項在列舉大量檔案時特別有用,它使 ls 不進入目錄內部檢視,把目錄也當作檔案來顯示。

4.9 檔案操作

檔案操作不外乎這麼幾種:建立檔案、複製檔案、移動檔案、刪除檔案。下面我們就來了解一下怎樣在 Linux 下實現這些操作。

一般情況下,程式會自動建立檔案,比如啟動一個編輯器寫文章,最後儲存的時候就會自動建立檔案。但如果我們一定要手動建立一個空檔案,可以使用 touch 命令。touch 命令的功能是在不改變檔案內容的情況下改變檔案的修改時間和訪問時間,就好像“摸”了檔案一下。例如:

$ touch /home/jim/document

會同時將 document 檔案的修改時間和訪問時間設定為當前時間。

如果 touch 命令指定的檔案不存在,touch 將建立這個檔案,所以其實是利用這個副作用來建立空檔案,例如:

$ touch file1

會在工作目錄下建立一個名為“file1”的空檔案,且該檔案的修改時間和訪問時間均為當前時間。

複製檔案使用的命令是 cp,語法為:

$ cp 已有檔名稱 目標檔名稱

舉一個例子,假如我們想要複製一份系統的口令檔案到“home”目錄下,且命名為 mypasswd,可以這樣寫:

$ cp /etc/passwd ~/mypasswd

cp 命令更常見的用法是將一個或多個檔案複製到一個目錄中,語法為:

$ cp 已有檔名稱… 已有目錄名稱

舉一個例子:假如現在的工作目錄是 /home/jim/project2,而我們需要將 /home/jim 目錄下的檔案 document 複製到工作目錄下,可以這樣寫:

$ cp ../document .

要將檔案移動到不同的目錄中,可以使用 mv (move)命令,語法為:

$ mv 已有檔名稱… 目標目錄名稱

如果想要重新命名檔案,也是使用 mv 命令,語法為:

$ mv 已有檔名稱 新的檔名稱

其實使用 mv 命令,可以同時重新命名和移動檔案。下面舉一個例子:

假如要將 /home/jim 目錄下的 document 檔案移動到 /home/jim/project2 目錄下,且重新命名為 mydocument,可以這樣寫:

$ cd ~
$ mv document project2/mydocument

因為 /home/jim 是當前使用者的“home”目錄,所以使用“~”。


【進階操作】
如果目標檔案已經存在,那麼 cp 或 mv 命令將覆蓋這個檔案,且無法恢復。為了在覆蓋已有的檔案之前進行詢問,可以使用 -i (interactive)選項,例如:

$ cp -i file1 file2

如果 file2 已經存在,會在覆蓋之前進行詢問,只有使用者確認後命令才會執行。


為了刪除檔案,需要使用 rm (remove)命令,語法為:

$ rm 要刪除的檔名稱…

【進階操作】
一旦檔案被刪除,它就永遠消失了,沒有任何方法可以找回刪除的檔案。因此可以使用 -i 選項,這樣 rm 在刪除每個檔案之前都會進行詢問,只有使用者確認後命令才會執行。

4.10 目錄操作

與檔案操作類似,目錄操作也不外乎這麼幾種:建立目錄、複製目錄、移動目錄、刪除目錄。下面我們就來了解一下怎樣在 Linux 下實現這些操作。

建立目錄,使用 mkdir 命令,語法為:

$ mkdir 要建立的目錄名稱

【進階操作】
預設情況下,如果父目錄不存在,就不能建立子目錄。可以使用 -p (make parent)選項,讓 mkdir 自動建立所有需要的父目錄。例如:

$ mkdir -p ~/dir1/dir2/dir3/dir4/test

mkdir 會自動建立 test 上層所有不存在的父目錄


複製目錄與檔案相似,也是使用 cp 命令。與檔案不同的是,複製目錄需要使用 -r 選項,可以讓 cp 將目錄及其所有檔案和子目錄複製到另一個目錄中,語法為:

$ cp -r 已有目錄名稱…  目標目錄名稱

與檔案相似,移動和重新命名目錄也是使用 mv 命令。

對於移動目錄來說,語法為:

$ mv 已有的目錄名稱… 目標目錄名稱

對於重新命名目錄來說,語法為:

$ mv 已有的目錄名稱 新的目錄名稱

若 mv 的目標目錄不存在,則已有目錄被重新命名為目標目錄。如果目標目錄已經存在,則已有目錄被移動到目標目錄下成為目標目錄的一個子目錄。

為了刪除目錄,可以使用 rmdir 程式,語法為:

$ rmdir 希望移除的目錄名稱…

但是這個命令非常雞肋,只能刪除空目錄,我更推薦下面的帶 -r 選項的 rm 命令:

$ rm -r 要刪除的目錄名稱…

會將目錄及其所有檔案和子目錄都一併刪除。刪除前一定要確認沒有刪錯,否則會造成嚴重的後果。


【進階操作】 與檔案操作類似,目錄操作中的 cp、mv、rm 命令也可以使用 -i 選項,這樣會在覆蓋或刪除檔案前給出提示,只有獲得使用者確認後才會執行。

4.11 萬用字元

因為我們在 shell 中總是頻繁地使用檔名,為了簡化輸入,shell 提供了一些特殊字元來幫助使用者快速指定一組檔名,這些特殊的字元叫做萬用字元。萬用字元只有一個簡單的用途:當鍵入一條命令時匹配一組檔名。

下面列出了一些基本的萬用字元及其含義:
*:匹配任何 0 個或多個字元構成的序列
?:匹配任何單個的字元
[list]:匹配 list 中的任何字元
[!list]:匹配不在 list 中的任何字元
{string1 | string2…}:匹配其中一個指定的字串

例如,想要列舉當前工作目錄中所有以字母“h”開頭的檔案,可以輸入:

$ ls h*

這裡 * 匹配 0 個或多個字元。

實際上,當使用萬用字元時,shell 將解釋模式,並在執行命令之前以合適的檔名替換模式。例如對於上面的命令,shell 會先將 h* 替換為所有以 h 開頭的檔名,然後再執行 ls 命令。萬用字元是 shell 提供的功能,只要是接受檔名作為引數的命令,都可以使用萬用字元。

下面是一些舉例:
X*g:以“X”開頭且以“g”結尾的檔名
d?:以“d”開頭的兩個字元的檔名
?*g:至少兩個字元,且以“g”結束的檔名

注意,使用 [list] 萬用字元時,只匹配一個字元。方括號還可以使用“-”指定範圍,比如 [0-9] 匹配任何 0 至 9 的數字。

[a-z]*:以小寫字母開頭的檔名
[a-zA-Z]*[0-9]:以大寫或小寫字母開頭,以數字結尾的檔名


【進階操作】 對於 C 排序序列(ABCD…abcd…)來說,[A-Z] 或 [a-z] 這些字元範圍表示法都是正確的,但某些情況下,使用的是字典排序序列(aAbBcCdD…zZ),這時就必須使用預定義字元類來表示了。

[[:lower:]]:匹配某個小寫字母,類似於 [a-z]
[[:upper:]]:匹配某個大寫字母,類似於 [A-Z]
[[:digit:]]:匹配某個數字,類似於 [0-9]
[[:alnum:]]:匹配某個大寫或小寫字母或數字,類似 [A-Za-z0-9]
[[:alpha:]]:匹配某個大寫或小寫字母,類似於 [A-Za-z]

4.12 連結

正如我們前面所說,在 Linux 檔案系統中,每個檔案都有一個對應的 inode,我們把檔名稱和它對應的 inode 之間的連線稱為連結。如果你對 inode 等概念感到生疏,可以回看 4.3 節。

我們首先要明確的是,在 Linux 檔案系統中,檔案的唯一識別符號是其 i 節點號,而不是其名稱。因為 Linux 檔案系統允許多重連結,所以一個檔案可以有多個名稱,即多個檔名稱都引向同一 i 節點號。

在 Linux 中,連結是基本檔案命令的基礎,在後面一節中我們會詳細討論。Linux 平等地對待所有的連結,它並不關心該檔案的原始名稱是什麼,也不關心連結建立的先後次序。

如果想為單個檔案建立新連結,語法為:

$ ln 已有檔名稱 新連結的名稱

例如,工作目錄下有一個檔案 a.txt,我們想為它建立一個名為 b.txt 的新連結,可以輸入:

$ ln a.txt b.txt

再用 ls -i 檢視一下,會看到這兩個檔名稱對應的 i 節點號是相同的。

如果想為多個檔案在指定目錄建立新連結,語法為:

$ ls 已有檔名稱… 放置新連結的目錄名稱

我們沿用上面的例子,假如工作目錄下還有一個名為 testdir 的目錄,我們想在它裡面建立 a.txt、b.txt 的新連結。可以寫成:

$ ln a.txt b.txt testdir

進入 testdir 可以看到兩個分別名為 a.txt、b.txt 的新連結。

為了檢視檔案的連結數,我們可以使用前面介紹過的 ls -l 長目錄列表顯示命令,連結的數量顯示在所有人的許可權和擁有者的名稱之間。如果你感到有些陌生,可以重新查閱一下 4.6 節的內容。

4.13 檔案操作的真實含義

在前面的幾節中,我們討論了 cp、mv、rm 等檔案操作命令的使用方法。對於學習 Linux 來說,瞭解它們背後的基本原理同樣重要。Linux 的魅力正體現在它許多獨特而又巧妙的設計之中,只有理解了這些檔案操作命令的工作方式,才能真正理解 Linux 的檔案系統。

Linux 在建立新檔案時,會在儲存裝置上留出相應的儲存空間並建立 inode。然後在適當的目錄中新增新條目,記錄檔案的名稱,以及其對應的 i 節點號。

複製檔案時,若目標檔案已存在,Linux 不會修改目標檔案的 i 節點號,而是直接用原始檔的內容覆蓋掉目標檔案的內容。若目標檔案不存在,Linux 會建立新的 i 節點號,並建立對應的全新檔案,再把原始檔的內容複製到新檔案中。

在使用 mv 命令時,不管是移動、改名或者是兩者同時進行,檔案的 i 節點號都不會改變。也就是說檔案本身並沒有任何變動,只是在目錄中新增、刪除對應的條目而已。這也正是 mv 命令既可以重新命名檔案,又可以移動檔案的原因。

當為已有檔案建立新的連結時,只是在適當的目錄下使用指定的檔名建立一個新的目錄條目,而它的 i 節點號與已有檔案相同,也就是說這個新的檔名和已有檔名指向的是同一個 inode。

當移除連結時,Linux 會移除對應的目錄條目,並消除檔名和 i 節點號之間的連線。這時,如果檔案已經沒有任何連結指向它,即所有目錄中對應該檔案的條目均被刪除,Linux 才會真正刪除這個檔案。所以,rm 命令實際上是移除檔案的連結,而不是真正地刪除檔案。如果說有兩個檔名稱指向同一個檔案,這時用 rm 命令刪除其中一個檔名,則原檔案依然存在。除非這兩個檔名都被刪除,否則原檔案不會被真正刪除。

4.14 符號連結

前面我們介紹的連結也被稱為硬連結(hard link),硬連結用來在檔案系統中為檔案命名。一旦建立一個硬連結,就為檔案建立了一個額外的條目。但是硬連結有兩個侷限性:第一,不能為目錄建立連結;第二,不能為不同檔案系統中的檔案建立連結。

為了克服硬連結的侷限性,Linux 還提供了符號連結(symbol link 或 symlink)。符號連結包含的不是檔案的 i 節點號,而是原檔案的路徑名,每當訪問符號連結時,Linux 藉助路徑名查詢檔案,所以它和 Windows 的快捷方式差不多。

建立符號連結與建立硬連結相似,只需使用帶 -s 選項的 ln 命令。例如,為工作目錄下的檔案 a.txt 建立一個名為 b.txt 的符號連結,可以這樣寫:

$ ln -s a.txt b.txt

建立後,可以使用 ls -l 檢視,會看到 b.txt 的檔案型別為 l,即符號連結,且顯示的是 b.txt -> a.txt,意味著 b.txt 指向的是 a.txt,而且 b.txt 這個連結檔案很小,它僅僅存放了一個路徑名“a.txt”。

也可以為目錄建立符號連結,例如工作目錄下有一個名為 testdir 的目錄,我們為它建立一個名為 testdirln 的符號連結:

$ ln -s testdir testdirln

會看到工作目錄下多了一個名為 testdirln 的目錄。

我希望你明白的是,例如 b.txt -> a.txt 顯示的是相對路徑名 a.txt,你也可以建立這樣的符號連結:

$ ln -s ../fun.txt testdir/funln.txt

會在工作目錄下的 testdir 目錄下建立一個名為 funln.txt 的符號連結,它指向工作目錄下的 fun.txt 檔案,這裡的 ../fun.txt 是相對於 funln.txt 而言的。

當然你也可以使用絕對路徑名建立符號連結:

$ ln -s /home/jim/a.txt testdir/aln.txt

會在工作目錄下的 testdir 目錄下建立一個名為 aln.txt 的符號連結,它指向 /home/jim/a.txt 檔案。

當你刪除一個符號連結時,只是這個連結被刪除,不會影響到原檔案。如果先於符號連結刪除它指向的原檔案,這個連結仍然存在,但是它不指向任何東西。這時,稱這個連結為壞連結,如果試圖使用該連結,會顯示錯誤訊息。

仔細思考一下,其實硬連結和符號連結有著本質上的不同。硬連結可以理解為是目錄中的一個條目,它本身沒有實體,不佔磁碟空間,僅僅是指向真實檔案的一個連線,所以可以直接把它看做是真實的檔案。只有所有指向真實檔案的硬連結都被刪除,真實檔案才會被刪除,所以不可能出現“壞連結”。而符號連結是一個實體檔案,佔磁碟空間,它存放一個檔案路徑名,訪問它時根據存放的路徑名來訪問指向的原檔案。只要路徑名對應的檔案不存在,它就變成了壞連結。

舉一個例子,假如有兩個硬連結 a.txt、b.txt 指向同一個真實的檔案,為 b.txt 建立一個符號連結 c.txt,那麼只要刪除硬連結 b.txt,符號連結 c.txt 就變成壞連結了,但真實的檔案依然存在。

4.15 locate & find - 查詢檔案

雖然 Linux 檔案系統有良好的組織結構,但因為檔案的數量實在過於龐大,我們難免還是會忘記檔案的存放位置,這時就需要進行檔案查詢。Linux 下有兩個常用的查詢檔案的命令:locate 和 find。locate 易於使用,而 find 功能更強大。

locate 程式的任務就是在一個特殊的資料庫(包含所有可公共訪問檔案的路徑名)中,查詢所有包含指定模式的路徑名。這個資料庫自動維護,並定期更新。

locate 命令非常簡單,只需要輸入:

$ locate 關鍵字

就會顯示所有路徑名中包含關鍵字的檔案。

但往往大多數時候,我們只需要匹配路徑名的最後一部分,即所謂的檔名或基名,這時就可以使用 -b 選項。例如,為了檢視所有基名中包含“temp”的檔案,可以輸入:

$ locate -b temp

正如前面提到的,locate 之所以查詢十分快速,是因為它只在路徑名資料庫中搜尋。這個資料庫會定時自動更新,所以當你建立新檔案時,新檔案並不會立即出現在資料庫中。為了手動更新路徑名資料庫,可以輸入:

$ sudo updatedb

locate 程式搜尋非常快速,而且易於使用,是搜尋檔案的首選程式。find 程式更加強大,但語法也更為複雜。

find 的思想是:搜尋一個或多個目錄樹,查詢滿足指定條件的檔案,然後對查詢到的檔案執行某種動作。所以 find 命令的語法為:

$ find 路徑… 測試條件… 執行動作…

find 會檢視每個路徑(包含所有子目錄),對遇到的每個檔案應用指定的測試條件,建立滿足標準的檔案列表。搜尋完成後,find 對列表中每個檔案執行指定的操作。下面舉一個例子:

$ find /home/jim -name temp -print

find 會顯示 /home/jim 目錄樹中所有命名為 temp 的檔案的路徑名。

對於路徑,既可以使用絕對路徑,也可以使用相對路徑。

對於測試條件,比較常用的有:
-name filename:檔名稱為 filename
-type [df]:檔案型別 d=目錄 f=普通檔案
-user userid:檔案擁有者為 userid

注意,-name 指定檔名時可以使用標準的萬用字元 *、? 和 [],但是必須將它們引用起來,從而防止它們在傳遞給 find 時被 shell 解釋。例如:

$ find . -type f -name '*.c' -print

這條命令會顯示工作目錄下所有 C 源程式檔案的路徑名,如果不加引號,shell 會將 *.c 解釋成一組實際檔名,從而產生語法錯誤。

對於動作,比較常用的有:
-print:將路徑名寫入到標準輸出
-ls:顯示長目錄列表
-delete:刪除檔案
-exec Command {} \;:執行 Command,{} 表示匹配的檔名

例如:

$ find ~ -type d -exec echo {} \;

find 會從“home”目錄開始搜尋,查詢所有的目錄,將搜尋結果傳送給 echo 程式,每次顯示一個結果。即每查詢到一個匹配項就會執行 -exec 動作,查詢到多少個目錄,echo 命令就被執行了多少次。且每次執行 echo 命令時,{} 都被不同目錄的路徑名取代。

4.2 一切皆檔案

Linux 中對檔案的定義非常寬泛,不僅包含我們一般所說的磁碟檔案,還包含物理裝置等偽檔案,所以 Linux 世界中有一句話叫做“一切皆檔案”。

對檔案下一個簡單的定義就是:檔案是一個輸入源或者一個輸出目標,我們可以從中讀取資料或者向其中寫入資料。因而像鍵盤(一種輸入源)、顯示器(一種輸出目標)等,都可以作為檔案被訪問。

這種寬泛的檔案定義方式為程式帶來了極大的方便,Linux 程式可以使用簡單的方法從任意的輸入源讀取資料,也可以向任意的輸出目標寫入資料。對使用者來說,這種定義方式也提供了極大的靈活性,使用者可以在執行程式的時候指定輸入源和輸出目標。

Linux 中的檔案可以分為 3 類:普通檔案、目錄檔案和偽檔案。

我們一般所說的檔案就是指普通檔案,普通檔案包含資料,存放在某種型別的儲存裝置上。普通檔案又可以分為 2 種型別:文字檔案和二進位制檔案。

  • 文字檔案就是由字元組成的檔案(包括行末不可見的換行符),例如純文字、shell 指令碼、源程式、HTML 檔案等等。
  • 二進位制檔案則包含非文字資料,這些資料只有在執行或由程式解釋時才有意義,例如可執行程式、影像、視訊、音樂等等。

接下來我們來看目錄檔案,目錄檔案其實與普通檔案類似,也存放在某種型別的儲存裝置上,但是它包含的不是常規資料。簡單地來說,目錄檔案中存放的是一些條目,分別記錄著目錄下的檔案和子目錄的相關資訊。Linux 系統通過這些條目來訪問目錄下的檔案和子目錄,關於訪問檔案的詳細情況,我們會在下一節中討論。

偽檔案則是最特殊的一種檔案,它既不儲存資料,也不佔用任何空間。偽檔案的作用是提供一種服務,這種服務採取和常規檔案相同的訪問方式進行訪問。偽檔案又可以分為 3 種型別:裝置檔案、命名管道和 proc 檔案。

  • 裝置檔案有時也被稱為特殊檔案,是物理裝置的內部表示,例如鍵盤、顯示器、磁碟驅動器等都可以作為裝置檔案來訪問。
  • 命名管道是我們以後要討論的管道的一個擴充套件,能夠將一個程式的輸出連線到另一個程式的輸入上。
  • proc 檔案則是用來訪問核心中的資訊。

    4.3 檔案儲存

首先我們介紹三個與檔案儲存相關的名詞:

  • 資料區塊(block/data block):存放檔案的實際資料,一個檔案可能佔用多個 block。
  • i 節點(inode):每個檔案都有一個對應的 inode,它記錄檔案的許可權和屬性,同時記錄檔案的實際資料所在的 block 號碼。
  • 超級區塊(superblock):記錄檔案系統的整體資訊,包括 inode/superblock 的總量、使用量、剩餘量,以及檔案系統的格式與相關資訊等。

簡而言之就是:檔案系統將檔案的許可權和屬性存放到 inode 中,將檔案的實際資料存放到 block 中。此外還有一個 superblock 會記錄整個檔案系統的整體資訊。

檔案系統將所有 inode 放在 inode 表中,並且賦予每個 inode 一個編號,稱為 i 節點號(inumber)。因為每個檔案都有一個對應的 inode,所以每個檔案都有唯一的 i 節點號。


【小提示】
通常一個檔案系統的最頂層 i 節點號為 2,即根目錄對應的 i 節點號為 2。
inode 的大小均固定為 128 位元組,這還不能全部用來記錄 block 號,而每記錄一個 block 號就要花掉 4 位元組,所以 Linux 將 inode 中記錄 block 號的資料區定義為 12 個直接、1 個間接、1 個雙間接和 1 個三間接記錄區,也就是使用多級指標結構來增加 inode 可記錄的檔案大小。
Ext2 檔案系統所支援的 block 大小有 1K、2K 以及 4K 三種,在格式化時 block 的大小就固定了,block 大小的不同會導致檔案系統能夠支援的最大磁碟容量和最大檔案大小不同。具體情況如下圖:

enter image description here
圖 4-3 block大小不同對應檔案系統的支援情況


當 Linux 建立一個檔案時,首先在儲存裝置上保留一塊空間用來儲存資料,然後為它建立一個 inode,用來存放檔案的基本資訊。由於每個 inode 與 block 都有編號,而每個 inode 中都存有檔案佔用的 block 的號碼,因此只要找到檔案對應的 i 節點號,再讀取 inode 中的內容,就能從對應的所有 block 中讀出檔案的實際資料。

想要檢視工作目錄下所有檔案的 i 節點號,可以輸入:

$ ls -i

從邏輯上看,目錄好像包含檔案,其實目錄只包含檔案的名稱以及檔案的 i 節點號。程式要訪問檔案,先在目錄中通過檔名稱找到對應的 i 節點號,然後在 i 節點表中找到對應的 i 節點,最後使用 i 節點中的資訊訪問檔案。

下面我們舉一個例子,比如我們想要讀取 /etc/passwd 檔案的內容(為簡單起見,忽略許可權的判斷):

  1. 根據根目錄 i 節點號為 2,在 inode 表中讀取根目錄的 inode 的內容。
  2. 根據根目錄 inode 中的記錄,讀取根目錄的 block,在其中找到 etc 目錄對應的 i 節點號。
  3. 根據 etc 目錄的 i 節點號讀取 etc 目錄的 inode 內容。
  4. 根據 etc 目錄 inode 中的記錄,讀取 etc 目錄的 block,在其中找到 passwd 檔案對應的 i 節點號。
  5. 根據 passwd 檔案的 i 節點號讀取 passwd 檔案的 inode 內容。
  6. 根據 passwd 檔案 inode 中的記錄,讀取 passwd 檔案的 block,完成檔案的讀取。

由此可見,實際上讀取檔案的過程還是比較複雜的。


【進階操作】
如果想要查詢當前作業系統中檔案儲存的相關情況,可以使用下面兩條命令。

檢視各個檔案系統的硬碟使用情況以及掛載點:

$ df -h

檢視硬碟的分割槽情況:

$ sudo fdisk -l

相關文章