在我們探究大多數鏈式 Bash 命令中出現的所有的雜項符號(&
、|
、;
、>
、<
、{
、[
、(
、)
、]
、}
等等)的任務中,我們一直在仔細研究 & 符號[1]。
上次,我們看到瞭如何使用 & 把可能需要很長時間執行的行程放到後臺執行[1]。但是,&
與尖括號 <
結合使用,也可用於將輸出或輸出透過管道導向其他地方。
在 前面的[2] 尖括號教程中[3],你看到瞭如何使用 >
,如下:
-
ls > list.txt
將 ls 輸出傳遞給 list.txt
檔案。
現在我們看到的是簡寫:
-
ls 1> list.txt
在這種情況下,1
是一個檔案描述符,指向標準輸出(stdout
)。
以類似的方式,2
指向標準錯誤輸出(stderr
):
-
ls 2> error.log
所有錯誤訊息都透過管道傳遞給 error.log
檔案。
回顧一下:1>
是標準輸出(stdout
),2>
是標準錯誤輸出(stderr
)。
第三個標準檔案描述符,0<
是標準輸入(stdin
)。你可以看到它是一個輸入,因為箭頭(<
)指向0
,而對於 1
和 2
,箭頭(>
)是指向外部的。
標準檔案描述符有什麼用?
如果你在閱讀本系列以後,你已經多次使用標準輸出(1>
)的簡寫形式:>
。
例如,當(假如)你知道你的命令會丟擲一個錯誤時,像 stderr
(2
)這樣的東西也很方便,但是 Bash 告訴你的東西是沒有用的,你不需要看到它。如果要在 home/
目錄中建立目錄,例如:
-
mkdir newdir
如果 newdir/
已經存在,mkdir 將顯示錯誤。但你為什麼要關心這些呢?(好吧,在某些情況下你可能會關心,但並非總是如此。)在一天結束時,newdir
會以某種方式讓你填入一些東西。你可以透過將錯誤訊息推入虛空(即 `/dev/null
)來抑制錯誤訊息:
-
mkdir newdir 2> /dev/null
這不僅僅是 “讓我們不要看到醜陋和無關的錯誤訊息,因為它們很煩人”,因為在某些情況下,錯誤訊息可能會在其他地方引起一連串錯誤。比如說,你想找到 /etc
下所有的 .service
檔案。你可以這樣做:
-
find /etc -iname "*.service"
但事實證明,在大多數系統中,find 顯示的錯誤會有許多行,因為普通使用者對 /etc
下的某些檔案夾沒有讀取訪問許可權。它使讀取正確的輸出變得很麻煩,如果 find 是更大的指令碼的一部分,它可能會導致行中的下一個命令排隊。
相反,你可以這樣做:
-
find /etc -iname "*.service" 2> /dev/null
而且你只得到你想要的結果。
檔案描述符入門
單獨的檔案描述符 stdout
和 stderr
還有一些註意事項。如果要將輸出儲存在檔案中,請執行以下操作:
-
find /etc -iname "*.service" 1> services.txt
工作正常,因為 1>
意味著 “傳送標準輸出且自身標準輸出(非標準錯誤)到某個地方”。
但這裡存在一個問題:如果你想把命令丟擲的錯誤資訊記錄到檔案,而結果中沒有錯誤資訊你該怎麼做?上面的命令並不會這樣做,因為它只寫入 find 正確的結果,而:
-
find /etc -iname "*.service" 2> services.txt
只會寫入命令丟擲的錯誤資訊。
我們如何得到兩者?請嘗試以下命令:
-
find /etc -iname "*.service" &> services.txt
…… 再次和 &
打個招呼!
我們一直在說 stdin
(0
)、stdout
(1
)和 stderr
(2
)是“檔案描述符”。檔案描述符是一種特殊構造,是指向檔案的通道,用於讀取或寫入,或兩者兼而有之。這來自於將所有內容都視為檔案的舊 UNIX 理念。想寫一個裝置?將其視為檔案。想寫入套接字並透過網路傳送資料?將其視為檔案。想要讀取和寫入檔案?嗯,顯然,將其視為檔案。
因此,在管理命令的輸出和錯誤的位置時,將標的視為檔案。因此,當你開啟它們來讀取和寫入它們時,它們都會獲得檔案描述符。
這是一個有趣的效果。例如,你可以將內容從一個檔案描述符傳遞到另一個檔案描述符:
-
find /etc -iname "*.service" 1> services.txt 2>&1
這會將 stderr
導向到 stdout
,而 stdout
透過管道被導向到一個檔案中 services.txt
中。
它再次出現:&
發訊號通知 Bash 1
是標的檔案描述符。
標準檔案描述符的另一個問題是,當你從一個管道傳輸到另一個時,你執行此操作的順序有點違反直覺。例如,按照上面的命令。它看起來像是錯誤的方式。你應該像這樣閱讀它:“將輸出導向到檔案,然後將錯誤導向到標準輸出。” 看起來錯誤輸出會在後面,並且在輸出到標準輸出(1
)已經完成時才傳送。
但這不是檔案描述符的工作方式。檔案描述符不是檔案的佔位符,而是檔案的輸入和(或)輸出通道。在這種情況下,當你做 1> services.txt
時,你的意思是 “開啟一個寫管道到 services.txt
並保持開啟狀態”。1
是你要使用的管道的名稱,它將保持開啟狀態直到該行的結尾。
如果你仍然認為這是錯誤的方法,試試這個:
-
find /etc -iname "*.service" 2>&1 1>services.txt
並註意它是如何不工作的;註意錯誤是如何被導向到終端的,而只有非錯誤的輸出(即 stdout
)被推送到 services.txt
。
這是因為 Bash 從左到右處理 find 的每個結果。這樣想:當 Bash 到達 2>&1
時,stdout
(1
)仍然是指向終端的通道。如果 find 給 Bash 的結果包含一個錯誤,它將被彈出到 2
,轉移到 1
,然後留在終端!
然後在命令結束時,Bash 看到你要開啟 stdout
(1
) 作為到 services.txt
檔案的通道。如果沒有發生錯誤,結果將透過通道 1
進入檔案。
相比之下,在:
-
find /etc -iname "*.service" 1>services.txt 2>&1
1
從一開始就指向 services.txt
,因此任何彈出到 2
的內容都會導向到 1
,而 1
已經指向最終去的位置 services.txt
,這就是它工作的原因。
在任何情況下,如上所述 &>
都是“標準輸出和標準錯誤”的縮寫,即 2>&1
。
這可能有點多,但不用擔心。重新導向檔案描述符在 Bash 命令列和指令碼中是司空見慣的事。隨著本系列的深入,你將瞭解更多關於檔案描述符的知識。下週見!