深入 Linux I/O 重定向

Mendel Cooper發表於2017-01-19

一個檔案描述符說白了就是檔案系統為了跟蹤這個開啟的檔案而分配給它的一個數字,也可以的將其理解為檔案指標的一個簡單版本,與C語言中檔案控制程式碼的概念很相似。

Linux 中預設情況下始終有 3 個“檔案”處於開啟狀態,stdin(鍵盤)、 stdout(螢幕)和 stderr(錯誤訊息輸出到螢幕上)。這 3 個檔案和其他開啟的檔案都可以被重定向。重定向,簡單的說就是捕捉一個檔案、命令、程式、指令碼,或者是指令碼中的程式碼塊的輸出,然後將這些輸出作為輸入傳送到另一個檔案、命令、程式或指令碼中。

每個開啟的檔案都會被分配一個檔案描述符。stdin、stdout 和 stderr 的檔案描述符分別是 0、1 和 2。除了這 3 個檔案,對於其它那些需要開啟的檔案,保留了檔案描述符 3 到 9。在某些情況下,將這些額外的檔案描述符分配給 stdin、stdout 或 stderr 作為臨時的副本連結是非常有用的。在經過複雜的重定向和重新整理之後需要把它們恢復成正常狀態。

重定向

> file

將 stdout 重定向到一個檔案。如果這個檔案不存在,那就建立,否則就覆蓋。

建立一個包含目錄樹列表的檔案:

ls -lR >dir-tree.list

清空檔案:

: > file

這是一個 > 操作,將會把檔案 file 變為一個空檔案(就是 size 為 0)。如果檔案不存在,那麼就建立一個 0 長度的檔案(與touch 的效果相同)。: 是一個佔位符,不產生任何輸出。

也可以省略 : 佔位符:

> file

與上邊的 : > 效果相同, 但是某些 shell (比如 bash)可能不支援這種形式。

>> file

將 stdout 重定向到一個檔案。如果檔案不存在,那麼就建立它,如果存在,那麼就追加到檔案後邊。

script.sh 1 > filename
# 重定向 stdout 到檔案"filename".
script.sh 1 >> filename
# 重定向並追加 stdout 到檔案"filename".
script.sh 2 > filename
# 重定向 stderr 到檔案"filename".
script.sh 2 >> filename
# 重定向並追加 stderr 到檔案"filename".

&> file

將 stdout 和 stderr 都重定向到檔案:

script.sh &> /dev/null

m> file

m 是一個檔案描述符,如果沒有明確指定的話預設為 1。

file 是一個檔名。檔案描述符 m 被重定向到檔案 file。

script.sh 2> error.log

m>&n

m 是一個檔案描述符,如果沒有明確指定的話預設為 1。

n 是另一個檔案描述符。

script.sh 2>&1

重定向 stderr 到 stdout。將錯誤訊息的輸出,傳送到與標準輸出所指向的地方。

exec 6<>File
script.sh >&6

預設的,重定向檔案描述符 1(stdout)到 6。所有傳遞到 stdout 的輸出都送到 6 中去。

< file

從檔案中接受輸入。與 > 是成對命令,並且通常都是結合使用。0 < file 或 < file,前面的標準輸入 stdin 0 可以省略。

grep search-word < filename

j<>file

為了讀寫 file,把檔案 file 開啟,並且將檔案描述符 j 分配給它。
如果檔案 file 不存在,那麼就建立它。如果檔案描述符 j 沒指定,那預設是標準輸入 stdin 0 。

echo 1234567890 > File ### 寫字串到 File .
exec 3<>File ### 開啟 File 並且將 fd 3 分配給它.
read -n 4 <&3 ### 只讀取4個字元.
echo -n . >&3 ### 寫一個小數點.
exec 3>&- ### 關閉fd 3.
cat File ### ==> 1234.67890

(注:上述命令的輸出結果和原文不同,原因未知。)

管道

管道與 > 很相似,但是實際上更通用。對於想將命令、指令碼、檔案和程式串連起來的時候很有用。

cat *.txt | sort | uniq > result-file

上述命令對所有 .txt 檔案的輸出進行排序,並且刪除重複行。最後將結果儲存到 result-file 中。

可以將輸入輸出重定向和/或管道的多個例項結合到一起寫在同一行上:

command < input-file > output-file

等價於:

< input-file command > output-file

但是這種寫法不標準,有的 shell 可能不支援。

可以將多個輸出流重定向到一個檔案上:

ls -yz >> command.log 2>&1

將錯誤選項 yz 的結果放到檔案 command.log 中。因為 stderr 被重定向到這個檔案中,所以所有的錯誤訊息也就都指向那裡了。
注意,下邊這個例子就不會給出相同的結果:

ls -yz 2>&1 >> command.log

輸出一個錯誤訊息,但是並不寫到檔案中。命令的輸出(如果有的話)寫入到檔案 command.log。

如果同時將 stdout 和 stderr 都重定向,命令的順序不同會帶來不同的結果。

關閉檔案描述符

n<&- 關閉輸入檔案描述符 n。

0<&- 或 <&- 關閉 stdin。

n>&- 關閉輸出檔案描述符 n。

1>&- 或 >&- 關閉 stdout。

子程式繼承了開啟的檔案描述符。這就是為什麼管道可以工作的原因。如果想阻止檔案描述符被繼承,那麼可以關掉它。

只將 stderr 重定到一個管道。

exec 3>&1 ### 儲存當前 stdout 的"值"(將 fd3 指向 fd0 相同目標)
ls -l 2>&1 >&3 3>&- | grep bad 3>&- ### 對'grep'關閉 fd 3
###            ^^^^   ^^^^          ###(但不關閉'ls',正常輸出內容不受grep影響)
ls -l 2>&1 >&3 | grep bad ### 這樣輸出內容被轉到了 fd3,也不會受 grep 影響
ls badabc -l 2>&1 >&3 |grep bad ### stderr 通過 fd1 輸出,會受 grep 影響
exec 3>&- ### 對於剩餘的指令碼來說,關閉它

使用檔案描述符 5 可能會引起問題。當 Bash 使用 exec 建立一個子程式的時候,子程式會繼承檔案描述符 5 (參考 Chet Ramey 的歸檔 e-mail: RE: File descriptor 5 is held open)。 最好還是不要去招惹這個特定的檔案描述符 5 。

 

相關文章