資料流重導向[轉]

來源:互聯網
上載者:User

標籤:


 資料流重導向

資料流重導向 (redirect) 由字面上的意思來看,好像就是將『資料給他傳導到其他地方去』的樣子? 沒錯~資料流重導向就是將某個命令運行後應該要出現在螢幕上的資料, 給他傳輸到其他的地方,例如檔案或者是裝置 (例如印表機之類的)!這玩意兒在 Linux 的文字模式底下可重要的! 尤其是如果我們想要將某些資料儲存下來時,就更有用了!

什麼是資料流重導向

什麼是資料流重導向啊?這得要由命令的運行結果談起!一般來說,如果你要運行一個命令,通常他會是這樣的:


圖 5.1.1、命令運行過程的資料轉送情況

我們運行一個命令的時候,這個命令可能會由檔案讀入資料,經過處理之後,再將資料輸出到螢幕上。 在當中, standard output 與 standard error output 分別代表『標準輸出』與『標準錯誤輸出』, 這兩個玩意兒預設都是輸出到螢幕上面來的啊!那麼什麼是標準輸出與標準錯誤輸出呢?

  • standard output 與 standard error output

簡單的說,標準輸出指的是『命令運行所回傳的正確的資訊』,而標準錯誤輸出可理解為『 命令運行失敗後,所回傳的錯誤資訊』。舉個簡單例子來說,我們的系統預設有 /etc/crontab 但卻無 /etc/vbirdsay, 此時若下達『 cat /etc/crontab /etc/vbirdsay 』這個命令時,cat 會進行:

  • 標準輸出:讀取 /etc/crontab 後,將該檔案內容顯示到螢幕上;
  • 標準錯誤輸出:因為無法找到 /etc/vbirdsay,因此在螢幕上顯示錯誤資訊

不管正確或錯誤的資料都是預設輸出到螢幕上,所以螢幕當然是亂亂的!那能不能透過某些機制將這兩股資料分開呢? 當然可以啊!那就是資料流重導向的功能啊!資料流重導向可以將 standard output (簡稱 stdout) 與 standard error output (簡稱 stderr) 分別傳送到其他的檔案或裝置去,而分別傳送所用的特殊字元則如下所示:

  1. 標準輸入  (stdin) :代碼為 0 ,使用 < 或 << ;
  2. 標準輸出  (stdout):代碼為 1 ,使用 > 或 >> ;
  3. 標準錯誤輸出(stderr):代碼為 2 ,使用 2> 或 2>> ;

為了理解 stdout 與 stderr ,我們先來進行一個範例的練習:

範例一:觀察你的系統根目錄 (/) 下各目錄的檔案名稱、許可權與屬性,並記錄下來[[email protected] ~]# ll /  <==此時螢幕會顯示出檔案名稱資訊[[email protected] ~]# ll / > ~/rootfile <==螢幕並無任何資訊[[email protected] ~]# ll  ~/rootfile <==有個新檔被建立了!-rw-r--r-- 1 root root 1089 Feb  6 17:00 /root/rootfile

怪了!螢幕怎麼會完全沒有資料呢?這是因為原本『 ll / 』所顯示的資料已經被重新導向到 ~/rootfile 檔案中了! 那個 ~/rootfile 的檔名可以隨便你取。如果你下達『 cat ~/rootfile 』那就可以看到原本應該在螢幕上面的資料囉。 如果我再次下達:『 ll /home > ~/rootfile 』後,那個 ~/rootfile 檔案的內容變成什嗎? 他將變成『僅有 ll /home 的資料』而已!咦!原本的『 ll / 』資料就不見了嗎?是的!因為該檔案的建立方式是:

  1. 該檔案 (本例中是 ~/rootfile) 若不存在,系統會自動的將他建立起來,但是
  2. 當這個檔案存在的時候,那麼系統就會先將這個檔案內容清空,然後再將資料寫入!
  3. 也就是若以 > 輸出到一個已存在的檔案中,那個檔案就會被覆蓋掉囉!

那如果我想要將資料累加而不想要將舊的資料刪除,那該如何是好?利用兩個大於的符號 (>>) 就好啦!以上面的範例來說,你應該要改成『 ll / >> ~/rootfile 』即可。 如此一來,當 (1) ~/rootfile 不存在時系統會主動建立這個檔案;(2)若該檔案已存在, 則資料會在該檔案的最下方累加進去!

上面談到的是 standard output 的正確資料,那如果是 standard error output 的錯誤資料呢?那就透過 2> 及 2>> 囉!同樣是覆蓋 (2>) 與累加 (2>>) 的特性!我們在剛剛才談到 stdout 代碼是 1 而 stderr 代碼是 2 , 所以這個 2> 是很容易理解的,而如果僅存在 > 時,則代表預設的代碼 1 囉!也就是說:

  • 1> :以覆蓋的方法將『正確的資料』輸出到指定的檔案或裝置上;
  • 1>>:以累加的方法將『正確的資料』輸出到指定的檔案或裝置上;
  • 2> :以覆蓋的方法將『錯誤的資料』輸出到指定的檔案或裝置上;
  • 2>>:以累加的方法將『錯誤的資料』輸出到指定的檔案或裝置上;

要注意喔,『 1>> 』以及『 2>> 』中間是沒有空格的!OK!有些概念之後讓我們繼續聊一聊這傢伙怎麼應用吧! 當你以一般身份運行 find 這個命令的時候,由於許可權的問題可能會產生一些錯誤資訊。例如運行『 find / -name testing 』時,可能會產生類似『 find: /root: Permission denied 』之類的資訊。 例如底下這個範例:

範例二:利用一般身份帳號搜尋 /home 底下是否有名為 .bashrc 的檔案存在[[email protected] ~]# su - dmtsai  <==假設我的系統有名為 dmtsai 的帳號[[email protected] ~]$ find /home -name .bashrc <==身份是 dmtsai 喔!find: /home/lost+found: Permission denied  <== Standard errorfind: /home/alex: Permission denied        <== Standard errorfind: /home/arod: Permission denied        <== Standard error/home/dmtsai/.bashrc                       <== Standard output

由於 /home 底下還有我們之前建立的帳號存在,那些帳號的家目錄你當然不能進入啊!所以就會有錯誤及正確資料了。 好了,那麼假如我想要將資料輸出到 list 這個檔案中呢?運行『 find /home -name .bashrc > list 』 會有什麼結果?呵呵,你會發現 list 裡面存了剛剛那個『正確』的輸出資料, 至於螢幕上還是會有錯誤的資訊出現呢!傷腦筋!如果想要將正確的與錯誤的資料分別存入不同的檔案中需要怎麼做?

範例三:承範例二,將 stdout 與 stderr 分存到不同的檔案去[[email protected] ~]$ find /home -name .bashrc > list_right 2> list_error

注意喔,此時『螢幕上不會出現任何資訊』!因為剛剛啟動並執行結果中,有 Permission 的那幾行錯誤資訊都會跑到 list_error 這個檔案中,至於正確的輸出資料則會存到 list_right 這個檔案中囉!這樣可以瞭解了嗎? 如果有點混亂的話,去休息一下再來看看吧!

  • /dev/null 垃圾桶黑洞裝置與特殊寫法

想象一下,如果我知道錯誤資訊會發生,所以要將錯誤資訊忽略掉而不顯示或儲存呢? 這個時候黑洞裝置 /dev/null 就很重要了!這個 /dev/null 可以吃掉任何導向這個裝置的資訊喔!將上述的範例修訂一下:

範例四:承範例三,將錯誤的資料丟棄,螢幕上顯示正確的資料[[email protected] ~]$ find /home -name .bashrc 2> /dev/null/home/dmtsai/.bashrc  <==只有 stdout 會顯示到螢幕上, stderr 被丟棄了

再想象一下,如果我要將正確與錯誤資料通通寫入同一個檔案去呢?這個時候就得要使用特殊的寫法了! 我們同樣用底下的案例來說明:

範例五:將命令的資料全部寫入名為 list 的檔案中[[email protected] ~]$ find /home -name .bashrc > list 2> list  <==錯誤[[email protected] ~]$ find /home -name .bashrc > list 2>&1     <==正確[[email protected] ~]$ find /home -name .bashrc &> list         <==正確

上述表格第一行錯誤的原因是,由於兩股資料同時寫入一個檔案,又沒有使用特殊的文法, 此時兩股資料可能會交叉寫入該檔案內,造成次序的錯亂。所以雖然最終 list 檔案還是會產生,但是裡面的資料排列就會怪怪的,而不是原本螢幕上的輸出排序。 至於寫入同一個檔案的特殊文法如上表所示,你可以使用 2>&1 也可以使用 &> ! 一般來說,鳥哥比較習慣使用 2>&1 的文法啦!

  • standard input : < 與 <<

瞭解了 stderr 與 stdout 後,那麼那個 < 又是什麼呀?呵呵!以最簡單的說法來說, 那就是『將原本需要由鍵盤輸入的資料,改由檔案內容來取代』的意思。 我們先由底下的 cat 命令操作來瞭解一下什麼叫做『鍵盤輸入』吧!

範例六:利用 cat 命令來建立一個檔案的簡單流程[[email protected] ~]# cat > catfiletestingcat file test<==這裡按下 [ctrl]+d 來離開[[email protected] ~]# cat catfiletestingcat file test

由於加入 > 在 cat 後,所以那個 catfile 會被主動的建立,而內容就是剛剛鍵盤上面輸入的那兩行資料了。 唔!那我能不能用純文字檔案取代鍵盤的輸入,也就是說,用某個檔案的內容來取代鍵盤的敲擊呢? 可以的!如下所示:

範例七:用 stdin 取代鍵盤的輸入以建立新檔案的簡單流程[[email protected] ~]# cat > catfile < ~/.bashrc[[email protected] ~]# ll catfile ~/.bashrc-rw-r--r-- 1 root root 194 Sep 26 13:36 /root/.bashrc-rw-r--r-- 1 root root 194 Feb  6 18:29 catfile# 注意看,這兩個檔案的大小會一模一樣!幾乎像是使用 cp 來複製一般!

這東西非常的有協助!尤其是用在類似 mail 這種命令的使用上。 理解 < 之後,再來則是怪可怕一把的 << 這個連續兩個小於的符號了。 他代表的是『結束的輸入字元』的意思!舉例來講:『我要用 cat 直接將輸入的資訊輸出到 catfile 中, 且當由鍵盤輸入 eof 時,該次輸入就結束』,那我可以這樣做:

[[email protected] ~]# cat > catfile << "eof"> This is a test.> OK now stop> eof  <==輸入這關鍵詞,立刻就結束而不需要輸入 [ctrl]+d[[email protected] ~]# cat catfileThis is a test.OK now stop     <==只有這兩行,不會存在關鍵詞那一行!

看到了嗎?利用 << 右側的控制字元,我們可以終止一次輸入, 而不必輸入 [crtl]+d 來結束哩!這對程式寫作很有協助喔!好了,那麼為何要使用命令輸出重導向呢?我們來說一說吧!

  • 螢幕輸出的資訊很重要,而且我們需要將他存下來的時候;
  • 背景運行中的程式,不希望他幹擾螢幕正常的輸出結果時;
  • 一些系統的例行命令 (例如寫在 /etc/crontab 中的檔案) 的運行結果,希望他可以存下來時;
  • 一些運行命令的可能已知錯誤資訊時,想以『 2> /dev/null 』將他丟掉時;
  • 錯誤資訊與正確資訊需要分別輸出時。

當然還有很多的功能的,最簡單的就是網友們常常問到的:『為何我的 root 都會收到系統 crontab 寄來的錯誤資訊呢』這個咚咚是常見的錯誤, 而如果我們已經知道這個錯誤資訊是可以忽略的時候,嗯!『 2> errorfile 』這個功能就很重要了吧! 瞭解了嗎?

命令啟動並執行判斷依據: ; , &&, ||

在某些情況下,很多命令我想要一次輸入去運行,而不想要分次運行時,該如何是好?基本上你有兩個選擇, 一個是透過第十三章要介紹的 shell script 撰寫指令碼去運行,一種則是透過底下的介紹來一次輸入多重命令喔!

  • cmd ; cmd (不考慮命令相關性的連續命令下達)

在某些時候,我們希望可以一次運行多個命令,例如在關機的時候我希望可以先運行兩次 sync 同步化寫入磁碟後才 shutdown 電腦,那麼可以怎麼作呢?這樣做呀:

[[email protected] ~]# sync; sync; shutdown -h now

在命令與命令中間利用分號 (;) 來隔開,這樣一來,分號前的命令運行完後就會立刻接著運行後面的命令了。 這真是方便啊~再來,換個角度來想,萬一我想要在某個目錄底下建立一個檔案,也就是說,如果該目錄存在的話, 那我才建立這個檔案,如果不存在,那就算了。也就是說這兩個命令彼此之間是有相關性的, 前一個命令是否成功的運行與後一個命令是否要運行有關!那就得動用到 && 或 || 囉!

  • $? (命令回傳值) 與 && 或 ||

如同上面談到的,兩個命令之間有相依性,而這個相依性主要判斷的地方就在於前一個命令啟動並執行結果是否正確。 還記得本章之前我們曾介紹過命令回傳值吧!嘿嘿!沒錯,您真聰明!就是透過這個回傳值啦! 再複習一次『若前一個命令啟動並執行結果為正確,在 Linux 底下會回傳一個 $? = 0 的值』。 那麼我們怎麼透過這個回傳值來判斷後續的命令是否要運行呢?這就得要藉由『 && 』及『 || 』的幫忙了! 注意喔,兩個 & 之間是沒有空格的!那個 | 則是 [Shift]+[\] 的按鍵結果。

命令下達情況 說明
cmd1 && cmd2 1. 若 cmd1 運行完畢且正確運行($?=0),則開始運行 cmd2。
2. 若 cmd1 運行完畢且為錯誤 ($?≠0),則 cmd2 不運行。
cmd1 || cmd2 1. 若 cmd1 運行完畢且正確運行($?=0),則 cmd2 不運行。
2. 若 cmd1 運行完畢且為錯誤 ($?≠0),則開始運行 cmd2。

上述的 cmd1 及 cmd2 都是命令。好了,回到我們剛剛假想的情況,就是想要: (1)先判斷一個目錄是否存在; (2)若存在才在該目錄底下建立一個檔案。由於我們尚未介紹如何判斷式 (test) 的使用,在這裡我們使用 ls 以及回傳值來判斷目錄是否存在啦! 讓我們進行底下這個練習看看:

範例一:使用 ls 查閱目錄 /tmp/abc 是否存在,若存在則用 touch 建立 /tmp/abc/hehe [[email protected] ~]# ls /tmp/abc && touch /tmp/abc/hehels: /tmp/abc: No such file or directory # ls 很乾脆的說明找不到該目錄,但並沒有 touch 的錯誤,表示 touch 並沒有運行[[email protected] ~]# mkdir /tmp/abc[[email protected] ~]# ls /tmp/abc && touch /tmp/abc/hehe[[email protected] ~]# ll /tmp/abc-rw-r--r-- 1 root root 0 Feb  7 12:43 hehe

看到了吧?如果 /tmp/abc 不存在時,touch 就不會被運行,若 /tmp/abc 存在的話,那麼 touch 就會開始運行囉! 很不錯用吧!不過,我們還得手動自行建立目錄,傷腦筋~能不能自動判斷,如果沒有該目錄就給予建立呢? 參考一下底下的例子先:

範例二:測試 /tmp/abc 是否存在,若不存在則予以建立,若存在就不作任何事情[[email protected] ~]# rm -r /tmp/abc                <==先刪除此目錄以方便測試[[email protected] ~]# ls /tmp/abc || mkdir /tmp/abcls: /tmp/abc: No such file or directory <==真的不存在喔![[email protected] ~]# ll /tmp/abc                  total 0                                 <==結果出現了!有進行 mkdir

如果你一再重複『 ls /tmp/abc || mkdir /tmp/abc 』畫面也不會出現重複 mkdir 的錯誤!這是因為 /tmp/abc 已經存在, 所以後續的 mkdir 就不會進行!這樣理解否?好了,讓我們再次的討論一下,如果我想要建立 /tmp/abc/hehe 這個檔案, 但我並不知道 /tmp/abc 是否存在,那該如何是好?試看看:

範例三:我不清楚 /tmp/abc 是否存在,但就是要建立 /tmp/abc/hehe 檔案[[email protected] ~]# ls /tmp/abc || mkdir /tmp/abc && touch /tmp/abc/hehe

上面這個範例三總是會建立 /tmp/abc/hehe 的喔!不論 /tmp/abc 是否存在。那麼範例三應該如何解釋呢? 由於Linux 底下的命令都是由左往右啟動並執行,所以範例三有幾種結果我們來分析一下:

  • (1)若 /tmp/abc 不存在故回傳 $?≠0,則 (2)因為 || 遇到非為 0 的 $? 故開始 mkdir /tmp/abc,由於 mkdir /tmp/abc 會成功進行,所以回傳 $?=0 (3)因為 && 遇到 $?=0 故會運行 touch /tmp/abc/hehe,最終 hehe 就被建立了;

  • (1)若 /tmp/abc 存在故回傳 $?=0,則 (2)因為 || 遇到 0 的 $? 不會進行,此時 $?=0 繼續向後傳,故 (3)因為 && 遇到 $?=0 就開始建立 /tmp/abc/hehe 了!最終 /tmp/abc/hehe 被建立起來。

整個流程圖示如下:


圖 5.2.1、 命令依序啟動並執行關係

上面這張圖顯示的兩股資料中,上方的線段為不存在 /tmp/abc 時所進行的命令列為,下方的線段則是存在 /tmp/abc 所在的命令列為。如上所述,下方線段由於存在 /tmp/abc 所以導致 $?=0 ,讓中間的 mkdir 就不運行了! 並將 $?=0 繼續往後傳給後續的 touch 去利用啦!瞭乎?在任何時刻你都可以拿上面這張圖作為示意! 讓我們來想想底下這個例題吧!

例題:以 ls 測試 /tmp/vbirding 是否存在,若存在則顯示 "exist" ,若不存在,則顯示 "not exist"!答:這又牽涉到邏輯判斷的問題,如果存在就顯示某個資料,若不存在就顯示其他資料,那我可以這樣做:
ls /tmp/vbirding && echo "exist" || echo "not exist"
意思是說,當 ls /tmp/vbirding 運行後,若正確,就運行 echo "exist" ,若有問題,就運行 echo "not exist" !那如果寫成如下的狀況會出現什嗎?
ls /tmp/vbirding || echo "not exist" && echo "exist"
這其實是有問題的,為什麼呢?由圖 5.2.1 的流程介紹我們知道命令是一個一個往後運行, 因此在上面的例子當中,如果 /tmp/vbirding 不存在時,他會進行如下動作:
  1. 若 ls /tmp/vbirding 不存在,因此回傳一個非為 0 的數值;
  2. 接下來經過 || 的判斷,發現前一個命令回傳非為 0 的數值,因此,程式開始運行 echo "not exist" ,而 echo "not exist" 程式肯定可以運行成功,因此會回傳一個 0 值給後面的命令;
  3. 經過 && 的判斷,咦!是 0 啊!所以就開始運行 echo "exist" 。
所以啊,嘿嘿!第二個例子裡面竟然會同時出現 not exist 與 exist 呢!真神奇~

經過這個例題的練習,你應該會瞭解,由於命令是一個接著一個去啟動並執行,因此,如果真要使用判斷, 那麼這個 && 與 || 的順序就不能搞錯。一般來說,假設判斷式有三個,也就是:

command1 && command2 || command3

而且順序通常不會變,因為一般來說, command2 與 command3 會放置肯定可以運行成功的命令, 因此,依據上面例題的邏輯分析,您就會曉得為何要如此放置囉~這很有用的啦!而且.....考試也很常考~

 

資料流重導向[轉]

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.