結合Linux檔案描述符談重定向,徹底理解重定向的本質!

2020-07-16 10:04:46
《Linux重定向》一節講解了輸入輸出重定向的各種寫法,並提到了檔案描述符的概念;《Linux檔案描述符》一節從底層剖析了檔案描述符的本質,它只不過是一個陣列下標。本節我們就將兩者結合起來,看看 Shell 是如何借助檔案描述符實現重定向的。

Linux 系統這個“傻帽”只有一根筋,每次讀寫檔案的時候,都從檔案描述符下手,通過檔案描述符找到檔案指標,然後進入開啟檔案表和 i-node 表,這兩個表裡面才真正儲存了與開啟檔案相關的各種資訊。

試想一下,如果我們改變了檔案指標的指向,不就改變了檔案描述符對應的真實檔案嗎?比如檔案描述符 1 本來對應顯示器,但是我們偷偷將檔案指標指向了 log.txt 檔案,那麼檔案描述符 1 也就和 log.txt 對應起來了。

檔案指標只不過是一個記憶體地址,修改它是輕而易舉的事情。檔案指標是檔案描述符和真實檔案之間最關鍵的“紐帶”,然而這條紐帶卻非常脆弱,很容易被修改。

Linux 系統提供的函數可以修改檔案指標,比如 dup()、dup2();Shell 也能修改檔案指標,輸入輸出重定向就是這麼乾的。

對,沒錯,輸入輸出重定向就是通過修改檔案指標實現的!更準確地說,發生重定向時,Linux 會用檔案描述符表(一個結構體陣列)中的一個元素給另一個元素賦值,或者用一個結構體變數給陣列元素賦值,整體上的資源開銷相當低。

你看,發生重定向的時候,檔案描述符並沒有改變,改變的是檔案描述符對應的檔案指標。對於標準輸出,Linux 系統始終向檔案描述符 1 中輸出內容,而不管它的檔案指標指向哪裡;只要我們修改了檔案指標,就能向任意檔案中輸出內容。

以下面的語句為例來說明:

echo "c.biancheng.net" 1>log.txt

檔案描述符表本質上是一個結構體陣列,假設這個結構體的名字叫做 FD。發生重定向時,Linux 系統首先會開啟 log.txt 檔案,並把各種資訊新增到 i-node 表和檔案開啟表,然後再建立一個 FD 變數(通過這個變數其實就能讀寫檔案了),並用這個變數給下標為 1 的陣列元素賦值,覆蓋原來的內容,這樣就改變了檔案指標的指向,完成了重定向。

Shell 對檔案描述符的操作

前面提到,>是輸出重定向符號,<是輸入重定向符號;更準確地說,它們應該叫做檔案描述符操作符。> 和 < 通過修改檔案描述符改變了檔案指標的指向,所以能夠實現重定向的功能。

除了 > 和 <,Shell 還是支援<>,它的效果是前面兩者的總和。

Shell 檔案描述符操作方法一覽表
分類 用法 說明
輸出 n>filename 以輸出的方式開啟檔案 filename,並繫結到檔案描述符 n。n 可以不寫,預設為 1,也即標準輸出檔案。
n>&m 用檔案描述符 m 修改檔案描述符 n,或者說用檔案描述符 m 的內容覆蓋檔案描述符 n,結果就是 n 和 m 都代表了同一個檔案,因為 n 和 m 的檔案指標都指向了同一個檔案。

因為使用的是>,所以 n 和 m 只能用作命令的輸出檔案。n 可以不寫,預設為 1。
n>&- 關閉檔案描述符 n 及其代表的檔案。n 可以不寫,預設為 1。
&>filename 將正確輸出結果和錯誤資訊全部重定向到 filename。
輸入 n<filename 以輸入的方式開啟檔案 filename,並繫結到檔案描述符 n。n 可以不寫,預設為 0,也即標準輸入檔案。
n<&m 類似於 n>&m,但是因為使用的是<,所以 n 和 m 只能用作命令的輸入檔案。n 可以不寫,預設為 0。
n<&- 關閉檔案描述符 n 及其代表的檔案。n 可以不寫,預設為 0。
輸入和輸出 n<>filename 同時以輸入和輸出的方式開啟檔案 filename,並繫結到檔案描述符 n,相當於 n>filename 和 n<filename 的總和。。n 可以不寫,預設為 0。

【範例1】前面的文章中提到了下面這種用法:

command >file 2>&1

它省略了檔案描述符 1,所以等價於:

command 1>file 2>&1

這個語句可以分成兩步:先執行1>file,讓檔案描述符 1 指向 file;再執行2>&1,用檔案描述符 1 修改檔案描述符 2,讓 2 和 1 的內容一樣。最終 1 和 2 都指向了同一個檔案,也就是 file。所以不管是向 1 還是向 2 中輸出內容,最終都輸出到 file 檔案中。

這裡需要注意執行順序,多個操作符在一起會從左往右依次執行。對於上面的語句,就是先執行1>file,再執行2>&1;如果寫作下面的形式,那就南轅北轍了:

command 2>&1 1>file

Shell 會先執行2>&1,這樣 1 和 2 都指向了標準錯誤輸出檔案,也即顯示器;接著執行1>file,這樣 1 就指向了 file 檔案,但是 2 依然指向顯示器。最終的結果是,正確的輸出結果輸出到了 file 檔案,錯誤資訊卻還是輸出到顯示器。

【範例2】一個比較奇葩的重定向寫法。

echo "C語言中文網" 10>log.txt >&10

先執行10>log.txt,開啟 log.txt,並給它分配檔案描述符 10;接著執行>&10,用檔案描述符 10 來修改檔案描述符 1(對於>,省略不寫的話預設為 1),讓 1 和 10 都指向 log.txt 檔案,最終的結果是向 log.txt 檔案中輸出內容。

這條語句其實等價於echo "C語言中文網" >log.txt,我之所以寫得這麼繞,是為了讓大家理解各種操作符的用法。

檔案描述符 10 只用了一次,我們在末尾最好將它關閉,這是一個好習慣。

echo "C語言中文網" 10>log.txt >&10 10>&-