Shell十三問之十一:>與

11.1

談到 I/O redirection ,不妨先讓我們認識一下 File Descriptor (FD) 。

程序的運算,在大部份情況下都是進行數據(data)的處理,

這些數據從哪讀進?又,送出到哪裏呢?

這就是 file descriptor (FD) 的功用了。

在 shell 程序中,最常使用的 FD 大概有三個,分別爲:

0: Standard Input (STDIN)

1: Standard Output (STDOUT)

2: Standard Error Output (STDERR)

在標準情況下,這些 FD 分別跟如下設備(device)關聯:

stdin(0): keyboard

stdout(1): monitor

stderr(2): monitor

我們可以用如下下命令測試一下:

代碼:

 

$ mail -s test root

this is a test mail.

please skip.

^d (同時按 crtl 跟 d 鍵)

很明顯,mail 程序所讀進的數據,就是從 stdin 也就是 keyboard 讀進的。

不過,不見得每個程序的 stdin 都跟 mail 一樣從 keyboard 讀進,

因爲程序作者可以從檔案參數讀進 stdin ,如:

代碼:

 

$ cat /etc/passwd

但,要是 cat 之後沒有檔案參數則又如何呢?

哦,請您自己玩玩看囉.... ^_^

代碼:

$ cat

(請留意數據輸出到哪裏去了,最後別忘了按 ^d 離開...)

至於 stdout 與 stderr ,嗯... 等我有空再續吧... ^_^

還是,有哪位前輩要來玩接龍呢?

--------------

11.2

沿文再續,書接上一回... ^_^

相信,經過上一個練習後,你對 stdin 與 stdout 應該不難理解吧?

然後,讓我們繼續看 stderr 好了。

事實上,stderr 沒甚麼難理解的:說穿了就是"錯誤信息"要往哪邊送而已...

比方說,若讀進的檔案參數是不存在的,那我們在 monitor 上就看到了:

代碼:

 

$ ls no.such.file

ls: no.such.file: No such file or directory

若,一個命令同時產生 stdout 與 stderr 呢?

那還不簡單,都送到 monitor 來就好了:

代碼:

 

$ touch my.file

$ ls my.file no.such.file

ls: no.such.file: No such file or directory

my.file

okay,至此,關於 FD 及其名稱、還有相關聯的設備,相信你已經沒問題了吧?

那好,接下來讓我們看看如何改變這些 FD 的預設數據信道,

我們可用 < 來改變讀進的數據信道(stdin),使之從指定的檔案讀進。

我們可用 > 來改變送出的數據信道(stdout, stderr),使之輸出到指定的檔案。

比方說:

代碼:

 

$ cat < my.file

就是從 my.file 讀進數據

代碼:

 

$ mail -s test root < /etc/passwd

則是從 /etc/passwd 讀進...

這樣一來,stdin 將不再是從 keyboard 讀進,而是從檔案讀進了...

嚴格來說,< 符號之前需要指定一個 FD 的(之間不能有空白),

但因爲 0 是 < 的默認值,因此 < 與 0< 是一樣的﹗

okay,這個好理解吧?

那,要是用兩個 << 又是啥呢?

這是所謂的 HERE Document ,它可以讓我們輸入一段文本,直到讀到 << 後指定的字符串。

比方說:

代碼:

 

$ cat <<FINISH

first line here

second line there

third line nowhere

FINISH

這樣的話,cat 會讀進 3 行句子,而無需從 keyboard 讀進數據且要等 ^d 結束輸入。

至於 > 又如何呢?

且聽下回分解....

--------------

11.3

okay,又到講古時間~~~

當你搞懂了 0< 原來就是改變 stdin 的數據輸入信道之後,相信要理解如下兩個 redirection 就不難了:

* 1>

* 2>

前者是改變 stdout 的數據輸出信道,後者是改變 stderr 的數據輸出信道。

兩者都是將原本要送出到 monitor 的數據轉向輸出到指定檔案去。

由於 1 是 > 的默認值,因此,1> 與 > 是相同的,都是改 stdout 。

用上次的 ls 例子來說明一下好了:

代碼:

 

$ ls my.file no.such.file 1>file.out

ls: no.such.file: No such file or directory

這樣 monitor 就只剩下 stderr 而已。因爲 stdout 給寫進 file.out 去了。

代碼:

 

$ ls my.file no.such.file 2>file.err

my.file

這樣 monitor 就只剩下 stdout ,因爲 stderr 寫進了 file.err 。

代碼:

 

$ ls my.file no.such.file 1>file.out 2>file.err

這樣 monitor 就啥也沒有,因爲 stdout 與 stderr 都給轉到檔案去了...

呵~~~ 看來要理解 > 一點也不難啦﹗是不?沒騙你吧? ^_^

不過,有些地方還是要注意一下的。

首先,是 file locking 的問題。比方如下這個例子:

代碼:

 

$ ls my.file no.such.file 1>file.both 2>file.both

從 file system 的角度來說,單一檔案在單一時間內,只能被單一的 FD 作寫入。

假如 stdout(1) 與 stderr(2) 都同時在寫入 file.both 的話,

則要看它們在寫入時否碰到同時競爭的情形了,基本上是"先搶先贏"的原則。

讓我們用周星馳式的"慢鏡頭"來看一下 stdout 與 stderr 同時寫入 file.out 的情形好了:

* 第 1, 2, 3 秒爲 stdout 寫入

* 第 3, 4, 5 秒爲 stderr 寫入

那麼,這時候 stderr 的第 3 秒所寫的數據就丟失掉了﹗

要是我們能控制 stderr 必須等 stdout 寫完再寫,或倒過來,stdout 等 stderr 寫完再寫,那問題就能解決。

但從技術上,較難掌控的,尤其是 FD 在作"長期性"的寫入時...

那,如何解決呢?所謂山不轉路轉、路不轉人轉嘛,

我們可以換一個思維:將 stderr 導進 stdout 或將 stdout 導進 sterr ,而不是大家在搶同一份檔案,不就行了﹗

bingo﹗就是這樣啦:

* 2>&1 就是將 stderr 並進 stdout 作輸出

* 1>&2 或 >&2 就是將 stdout 並進 stderr 作輸出

於是,前面的錯誤操作可以改爲:

代碼:

 

$ ls my.file no.such.file 1>file.both 2>&1

$ ls my.file no.such.file 2>file.both >&2

這樣,不就皆大歡喜了嗎? 呵~~~ ^_^

不過,光解決了 locking 的問題還不夠,我們還有其它技巧需要了解的。

故事還沒結束,別走開﹗廣告後,我們再回來...﹗

--------------

11.4

okay,這次不講 I/O Redirction ,講佛吧...

(有沒搞錯?﹗網中人是否頭殼燒壞了?...) 嘻~~~ ^_^

學佛的最高境界,就是"四大皆空"。至於是空哪四大塊?我也不知,因爲我還沒到那境界...

但這個"空"字,卻非常值得我們返復把玩的:

--- 色即是空、空即是色﹗

好了,施主要是能夠領會"空"的禪意,那離修成正果不遠矣~~~

在 Linux 檔案系統裏,有個設備檔位於 /dev/null 。

許多人都問過我那是甚麼玩意兒?我跟你說好了:那就是"空"啦﹗

沒錯﹗空空如也的空就是 null 了.... 請問施主是否忽然有所頓誤了呢?然則恭喜了~~~ ^_^

這個 null 在 I/O Redirection 中可有用得很呢:

* 若將 FD1 跟 FD2 轉到 /dev/null 去,就可將 stdout 與 stderr 弄不見掉。

* 若將 FD0 接到 /dev/null 來,那就是讀進 nothing 。

比方說,當我們在執行一個程序時,畫面會同時送出 stdout 跟 stderr ,

假如你不想看到 stderr (也不想存到檔案去),那可以:

代碼:

 $ ls my.file no.such.file 2>/dev/null

my.file

若要相反:只想看到 stderr 呢?還不簡單﹗將 stdout 弄到 null 就行:

代碼:

 

$ ls my.file no.such.file >/dev/null

ls: no.such.file: No such file or directory

那接下來,假如單純只跑程序,不想看到任何輸出結果呢?

哦,這裏留了一手上次節目沒講的法子,專門贈予有緣人﹗... ^_^

除了用 >/dev/null 2>&1 之外,你還可以如此:

代碼:

 

$ ls my.file no.such.file &>/dev/null

(提示:將 &> 換成 >& 也行啦~~! )

okay?講完佛,接下來,再讓我們看看如下情況:

代碼:

 

$ echo "1" > file.out

$ cat file.out

1

$ echo "2" > file.out

$ cat file.out

2

看來,我們在重導 stdout 或 stderr 進一份檔案時,似乎永遠只獲得最後一次導入的結果。

那,之前的內容呢?

呵~~~ 要解決這個問提很簡單啦,將 > 換成 >> 就好:

代碼:

 

$ echo "3" >> file.out

$ cat file.out

2

3

如此一來,被重導的目標檔案之內容並不會失去,而新的內容則一直增加在最後面去。

easy ? 呵 ... ^_^

但,只要你再一次用回單一的 > 來重導的話,那麼,舊的內容還是會被"洗"掉的﹗

這時,你要如何避免呢?

----備份﹗ yes ,我聽到了﹗不過.... 還有更好的嗎?

既然與施主這麼有緣份,老納就送你一個錦囊妙法吧:

代碼:

 

$ set -o noclobber

$ echo "4" > file.out

-bash: file: cannot overwrite existing file

那,要如何取消這個"限制"呢?

哦,將 set -o 換成 set +o 就行:

代碼:

 

$ set +o noclobber

$ echo "5" > file.out

$ cat file.out

5

再問:那... 有辦法不取消而又"臨時"蓋寫目標檔案嗎?

哦,佛曰:不可告也﹗

啊~~~ 開玩笑的、開玩笑的啦~~~ ^_^ 唉,早就料到人心是不足的了﹗

代碼:

 

$ set -o noclobber

$ echo "6" >| file.out

$ cat file.out

6

留意到沒有:在 > 後面再加個" | "就好(注意: > 與 | 之間不能有空白哦)....

呼.... (深呼吸吐納一下吧)~~~ ^_^

再來還有一個難題要你去參透的呢:

代碼:

 

$ echo "some text here" > file

$ cat < file

some text here

$ cat < file > file.bak

$ cat < file.bak

some text here

$ cat < file > file

$ cat < file

嗯?﹗注意到沒有?﹗﹗

---- 怎麼最後那個 cat 命令看到的 file 竟是空的?﹗

why? why? why?  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章