深入了解Linux I/O重定向
一個(gè)文件描述符說(shuō)白了就是文件系統(tǒng)為了跟蹤這個(gè)打開(kāi)的文件而分配給它的一個(gè)數(shù)字,也可以的將其理解為文件指針的一個(gè)簡(jiǎn)單版本,與C語(yǔ)言中文件句柄的概念很相似。
Linux 中默認(rèn)情況下始終有 3 個(gè)“文件”處于打開(kāi)狀態(tài),stdin(鍵盤(pán))、 stdout(屏幕)和 stderr(錯(cuò)誤消息輸出到屏幕上)。這 3 個(gè)文件和其他打開(kāi)的文件都可以被重定向。重定向,簡(jiǎn)單的說(shuō)就是捕捉一個(gè)文件、命令、程序、腳本,或者是腳本中的代碼塊的輸出,然后將這些輸出作為輸入發(fā)送到另一個(gè)文件、命令、程序或腳本中。
每個(gè)打開(kāi)的文件都會(huì)被分配一個(gè)文件描述符。stdin、stdout 和 stderr 的文件描述符分別是 0、1 和 2。除了這 3 個(gè)文件,對(duì)于其它那些需要打開(kāi)的文件,保留了文件描述符 3 到 9。在某些情況下,將這些額外的文件描述符分配給 stdin、stdout 或 stderr 作為臨時(shí)的副本鏈接是非常有用的。在經(jīng)過(guò)復(fù)雜的重定向和刷新之后需要把它們恢復(fù)成正常狀態(tài)。
重定向
> file
將 stdout 重定向到一個(gè)文件。如果這個(gè)文件不存在,那就創(chuàng)建,否則就覆蓋。
創(chuàng)建一個(gè)包含目錄樹(shù)列表的文件:
- ls -lR >dir-tree.list
清空文件:
- : > file
這是一個(gè) > 操作,將會(huì)把文件 file 變?yōu)橐粋€(gè)空文件(就是 size 為 0)。如果文件不存在,那么就創(chuàng)建一個(gè) 0 長(zhǎng)度的文件(與touch 的效果相同)。: 是一個(gè)占位符,不產(chǎn)生任何輸出。
也可以省略 : 占位符:
- > file
與上邊的 : > 效果相同, 但是某些 shell (比如 bash)可能不支持這種形式。
>> file
將 stdout 重定向到一個(gè)文件。如果文件不存在,那么就創(chuàng)建它,如果存在,那么就追加到文件后邊。
- 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 是一個(gè)文件描述符,如果沒(méi)有明確指定的話默認(rèn)為 1。
file 是一個(gè)文件名。文件描述符 m 被重定向到文件 file。
- script.sh 2> error.log
m>&n
m 是一個(gè)文件描述符,如果沒(méi)有明確指定的話默認(rèn)為 1。
n 是另一個(gè)文件描述符。
- script.sh 2>&1
重定向 stderr 到 stdout。將錯(cuò)誤消息的輸出,發(fā)送到與標(biāo)準(zhǔn)輸出所指向的地方。
- exec 6<>File
- script.sh >&6
默認(rèn)的,重定向文件描述符 1(stdout)到 6。所有傳遞到 stdout 的輸出都送到 6 中去。
< file
從文件中接受輸入。與 > 是成對(duì)命令,并且通常都是結(jié)合使用。0 < file 或 < file,前面的標(biāo)準(zhǔn)輸入 stdin 0 可以省略。
- grep search-word < filename
j<>file
為了讀寫(xiě) file,把文件 file 打開(kāi),并且將文件描述符 j 分配給它。
如果文件 file 不存在,那么就創(chuàng)建它。如果文件描述符 j 沒(méi)指定,那默認(rèn)是標(biāo)準(zhǔn)輸入 stdin 0 。
- echo 1234567890 > File ### 寫(xiě)字符串到 File .
- exec 3<>File ### 打開(kāi) File 并且將 fd 3 分配給它.
- read -n 4 <&3 ### 只讀取4個(gè)字符.
- echo -n . >&3 ### 寫(xiě)一個(gè)小數(shù)點(diǎn).
- exec 3>&- ### 關(guān)閉fd 3.
- cat File ### ==> 1234.67890
(注:上述命令的輸出結(jié)果和原文不同,原因未知。)
管道
管道與 > 很相似,但是實(shí)際上更通用。對(duì)于想將命令、腳本、文件和程序串連起來(lái)的時(shí)候很有用。
- cat *.txt | sort | uniq > result-file
上述命令對(duì)所有 .txt 文件的輸出進(jìn)行排序,并且刪除重復(fù)行。***將結(jié)果保存到 result-file 中。
可以將輸入輸出重定向和/或管道的多個(gè)實(shí)例結(jié)合到一起寫(xiě)在同一行上:
- command < input-file > output-file
等價(jià)于:
- < input-file command > output-file
但是這種寫(xiě)法不標(biāo)準(zhǔn),有的 shell 可能不支持。
可以將多個(gè)輸出流重定向到一個(gè)文件上:
- ls -yz >> command.log 2>&1
將錯(cuò)誤選項(xiàng) yz 的結(jié)果放到文件 command.log 中。因?yàn)?stderr 被重定向到這個(gè)文件中,所以所有的錯(cuò)誤消息也就都指向那里了。
注意,下邊這個(gè)例子就不會(huì)給出相同的結(jié)果:
- ls -yz 2>&1 >> command.log
輸出一個(gè)錯(cuò)誤消息,但是并不寫(xiě)到文件中。命令的輸出(如果有的話)寫(xiě)入到文件 command.log。
如果同時(shí)將 stdout 和 stderr 都重定向,命令的順序不同會(huì)帶來(lái)不同的結(jié)果。
關(guān)閉文件描述符
- n<&- 關(guān)閉輸入文件描述符 n。
- 0<&- 或 <&- 關(guān)閉 stdin。
- n>&- 關(guān)閉輸出文件描述符 n。
- 1>&- 或 >&- 關(guān)閉 stdout。
子進(jìn)程繼承了打開(kāi)的文件描述符。這就是為什么管道可以工作的原因。如果想阻止文件描述符被繼承,那么可以關(guān)掉它。
只將 stderr 重定到一個(gè)管道。
- exec 3>&1 ### 保存當(dāng)前 stdout 的"值"(將 fd3 指向 fd0 相同目標(biāo))
- ls -l 2>&1 >&3 3>&- | grep bad 3>&-### 對(duì)'grep'關(guān)閉 fd 3
- ### ^^^^ ^^^^ ###(但不關(guān)閉'ls',正常輸出內(nèi)容不受grep影響)
- ls -l 2>&1 >&3 | grep bad ### 這樣輸出內(nèi)容被轉(zhuǎn)到了 fd3,也不會(huì)受 grep 影響
- ls badabc -l 2>&1 >&3 |grep bad ### stderr 通過(guò) fd1 輸出,會(huì)受 grep 影響
- exec 3>&- ### 對(duì)于剩余的腳本來(lái)說(shuō),關(guān)閉它
使用文件描述符 5 可能會(huì)引起問(wèn)題。當(dāng) Bash 使用 exec 創(chuàng)建一個(gè)子進(jìn)程的時(shí)候,子進(jìn)程會(huì)繼承文件描述符 5 (參考 Chet Ramey 的歸檔 e-mail: RE: File descriptor 5 is held open)。 ***還是不要去招惹這個(gè)特定的文件描述符 5 。