[分享] 研究 2>&1 後的一點點心得

歡迎提問 debian desktop 相關問題,何謂 desktop ? 舉凡您日常生活會用到的部份,如上網 ( www 、 bbs ..) 、程式設計、繪圖...等等。 通常以 X Window 環境底下問題為主。

[分享] 研究 2>&1 後的一點點心得

文章leewc6734 » 週三 3月 20, 2013 6:52 pm

這篇只是小弟的一點點心得,但對我來說卻是非常重要的概念釐清
最近在閱讀他人bash script時看見一個特殊的重新導向 "2>&1" ,試了一下卻根本搞不懂這到底是怎麼回事....
本來想說沒關係,直接跳過( :ooops: 很壞的習慣),但是發現怎麼後面一大堆這玩意兒,
所以就狠下心來好好地研究了一下,但這是我研究後的理解,要是有錯誤的話還請大家多多指教

===============
程式區段 - 節錄
===============
代碼: 選擇全部
ls -l test.txt > /dev/null 2>&1


這部份我一直看不懂,後來測試後終於知道他的意思了....馬的,學習 Bash 真的相當的挫折(密技太多了...)
先解釋這段 "ls -l test.txt > /dev/null 2>&1",UNIX 中系統將標準輸入輸出分成三個:
標準輸入 (stdin)、標準輸出 (stdout)、以及標準錯誤輸出 (stderr),而分別有數字代表

  • 標準輸入 (stdin) = 0
  • 標準輸出 (stdout) = 1
  • 標準錯誤輸出 (stderr) = 2

而以上的數字與我們在執行完指令後的結果會產生的 0 與 1 不同:
  • 指令完成並且沒有發生錯誤 = 0
  • 指令完成但有發生錯誤 = 1

當輸入一個指令,例如:ls -l,然後在輸入 echo $? 進行查詢可得知指令執行的結果:
0 代表指令完成並且沒有發生任何錯誤,而 1 代表指令雖完成了但出現了錯誤狀況!

聽得很混亂是嗎?這邊只是要釐清觀念,請耐心繼續看下去

這邊釐清了以後,讓我們回頭再解釋 "標準輸入(0)"、"標準輸出(1)" 與 "標準錯誤輸出(2)" 的意思,

當我們執行一個指令,例如:
代碼: 選擇全部
# ls -l > /dev/null


ls -l --> 將 > 左方的指令執行完成的 "標準輸出(1)" 結果轉向送到到右方的 /dev/null
/dev/null --> 由 > 右方透過 "標準輸入(0)" 來接收從左方 ls -l 指令的執行結果並輸入到 /dev/null 中

[解釋] 在 UNIX 系統中只要將標準輸出指向 /dev/null 時,一般在指令結束後會出現的一些文字就會
被直接 /dev/null 這個 device 吃掉,這是一個相當特別的裝置,就像黑洞一樣....哈

所以在一般情況下, ls 後你可以馬上看見輸出到 terminal 上的結果,
但這邊不會...當上面 ls -l 指令執行完成後,輸出的結果會透過 > 轉送到右方的 /dev/null 這個黑洞了...

[重點] 其實....剛剛讀了 bash manual 後發現:
代碼: 選擇全部
# ls -l > tmpfile.log
# ls -l 1>tmpfile.log


以下兩個指令意義上完全相同,都會將 ls -l 的結果轉向輸出到 tmpfile.log 中,平常我在下 redirect
指令時都是用第一種方式執行!但事實上完整指令應該是 ls -l 1>tmpfile.log,這句指令以我的理解應該是:

ls -l 執行完成後會透過 stdout(1) 送交到 terminal 上顯示給使用者,但這邊不會,因為我們使用了
1>tmpfile.log 指令將 stdout(1) 的結果直接轉向送進了 tmpfile.log 檔案中!
試試看!你會發現我說得狀況!

接下來我們繼續解釋 2>&1 這個指令的意思,假設一個狀況,今天執行一個指令但有指令因為某些狀況發生了錯誤,
例如:我們去 ls 一個根本不存在的檔案則就算你將輸出導向 /dev/null ,但錯誤訊息還是會輸出到 terminal 上,例如:
代碼: 選擇全部
# ls -l test.txt > /dev/null
ls: 無法存取 minicom.log1: 沒有此一檔案或目錄


為何會這樣?我原本以為當轉向到 /dev/null 後應該會連錯誤訊息都不會輸出到 terminal 上了!?

其實,是因為這個 ls 指令在執行時出現錯誤了,系統本來就會直接透過 stderr(2) 輸出到了 terminal,
而程式本身則繼續執行並完成了 > /dev/null ,可是,因為所執行的這個指令失敗導致 strout(1) 不會有任何東西輸出,
所以自然也不會有任何訊息或結果被送進 /dev/null 裡面了

這樣也許用了一個不好的範例來作解釋,因為 /dev/null 本來就會吃掉所有的訊息所以我們看不見結果,
也感覺不出到底有什麼差異...Orz
讓我們換個例子並分割每個步驟與結果來解釋,請看以下範例:
代碼: 選擇全部
# ls -l test.txt > test.err
ls: 無法存取 test.txt: 沒有此一檔案或目錄


這時你會發現出現一個錯誤而系統將錯誤訊息輸出到 terminal 上,但令人驚訝的是雖然有錯誤但 test.err 卻同時也有被系統建立,
所以應該代表系統有完整的執行整條指令,有錯誤輸出錯誤並且要建立檔案也同時建立,但....奇怪的是並沒有任何結果或錯誤訊息被寫入 test.err?

一開始我一直搞不懂,一直以為 "ls: 無法存取 test.txt: 沒有此一檔案或目錄" 這段錯誤訊息不是
應該也會被一併寫進 test.err 中嗎? 但 cat test.err 卻完全空白一片,還以為我查到的說明資料是錯的,
但仔細想想其實並非如此...是我誤解了,我將 stdout(1) 與 stderr(2) 的行為完全搞混了.....
原因在於,輸出到 terminal 上的東西是屬於「錯誤訊息」,它乃是屬於 stderr(2) 的部份,
而我們要寫入的內容應該是「在指令正確執行且未發生任何錯誤下交由 stdout(1) 輸出出來的結果」才對,
這根本是兩個不同的東西,我怎麼會把兩個混在一起了 = = ....

後來發現是自己的邏輯有問題,上面的這段指令沒有正確的執行結果被輸出,所以根本不會有任何的訊息
從 stdout(1) 被送出來,那又怎麼會有資料轉向送進 test.err 檔案中勒@@

這樣我終於了解,我希望寫入的資料它是從 "標準輸出stdout(1)" 輸出的結果!而從 stderr(2) 送到
terminal 上的錯誤訊息根本就不會如同 "標準輸出stdout(1)" 一樣寫進檔案中,「錯誤訊息 stderr(2)」根本和我們預期
「指令在正確執行後會被輸出的內容 "標準輸出stdout(1)" 」沒有任何關係,所以這條指令執行完 stdout(1) 那邊根本是空的,
又怎麼會有東西可以輸入到 test.err 勒?

但是,若你是執行:
代碼: 選擇全部
# ls -l test.txt > test.err 2>&1


這是什麼意思?其實上面的解釋如果看懂了,這段解釋就會很簡單了,解釋如下:

** 先假設 test.txt 不存在,這條指令執行後會有錯誤訊息 **
1. 執行 ls -l test.txt
2. 發生錯誤,錯誤訊息透過 stderr(2) 處理
3. 執行完畢但沒有結果,stdout(1) 沒有任何結果訊息可被輸出

但!重點來啦~輪到解釋 2>&1

4. 系統看見 2>&1 以後,反而會將步驟2 取得的 stderr(2) 錯誤訊息轉向送給 stdout(1) 並寫進 test.err 了

很神奇!用這種方式迂迴的將 stderr 不輸出到 terminal 中,而是將 stderr(2) 的錯誤訊息再寫回進 test.err 檔案中,

流程如下:
run cmd --> err happen --> no msg in stdout --> sent stdout content to test.err -->
stderr(2) content redirect to stdout(1) --> msg in stdout --> sent stdout content to test.err again

這樣執行完成後你會發現 test.err 裡面有訊息了,而且是原本應該輸出到 terminal 的錯誤訊息
被寫入到了 test.err 中了,這也是我用輸出到檔案的方式才發現的,一直往 /dev/null 送根本不知道
到底發生了什麼事.....馬的!

呼.....感謝各位耐心的看完小弟的文章,因為潛水太久 po 文格式也許不太人性化,真的不好意思~請多多包涵!
希望這篇不會誤導也希望能對像小弟一般的bash新手有點幫助! ^^
leewc6734
可愛的小學生
可愛的小學生
 
文章: 8
註冊時間: 週日 4月 04, 2004 1:15 pm

回到 debian desktop

誰在線上

正在瀏覽這個版面的使用者:沒有註冊會員 和 1 位訪客