shell十三問之11:>與< 差在哪?
這次的題目,之前我在CU的shell版說明過了: (原帖的連接在論壇改版後,已經失效) 這次我就不重寫了,將帖子的內容“抄”下來就是了...
1. 文件描述符(fd, File Descriptor)
談到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): keyboardstdout
(1): monitorstderr
(2): monitor
Tips: linux中的文件描述符(fd)用整數表示。 linux中任何一個進程都默認打開三個文件, 這三個文件對應的文件描述符分別是:0, 1, 2; 即stdin, stdout, stderr.
我們可以用如下命令測試一下:
$ mail -s test root
this is a test mail。
please skip.
^d (同時按下ctrl 跟d鍵)
很明顯,mail
進程所讀進的數據,就是從
stdin
也就是keyboard讀進的。
不過,不見得每個進程的stdin
都跟mail
一樣
從keyboard
讀進,因為進程的作者可以從文件參數讀進stdin
,
如:
$ cat /etc/passwd
但,要是cat
之後沒有文件參數則如何呢?
哦, 請你自己玩玩看...^_^
$ cat
Tips:
請留意數據輸出到哪裡去了, 最後別忘了按
ctrl+d
(^d
), 退出stdin輸入。
至於stdout
與stderr
,嗯...等我有空再續吧...^_^
還是,有哪位前輩來玩接龍呢?
相信,經過上一個練習後,
你對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 on.such.file
ls: no.such.file: No such file or directory
my.file
okay, 至此,關於fd及其名稱、還有相關聯的設備, 相信你已經沒問題了吧?
2. I/O 重定向(I/O Redirection)
那好,接下來讓我們看看如何改變這些fd的預設數據通道。
- 用
<
來改變讀進的數據通道(stdin),使之從指定的文件讀進。 - 用
>
來改變輸出的數據通道(stdout,stderr),使之輸出到指定的文件。
2.1 輸入重定向n<
(input redirection)
比方說:
$ cat < my.file
就是從my.file讀入數據
$ mail -s test root < /etc/passwd
則是從/etc/passwd讀入...
這樣一來,stdin將不再是從keyboard讀入, 而是從指定的文件讀入了...
嚴格來說,<
符號之前需要指定一個fd的(之前不能有空白),但因為0是<
的預設值,因此,<
與0<
是一樣的*。
okay,這樣好理解了吧?
那要是用兩個<
,即<<
又是啥呢?
這是所謂的here document
,
它可以讓我們輸入一段文本,
直到讀到<<
後指定的字符串。
比方說:
$ cat <
這樣的話, cat
會讀入3個句子,
而無需從keyboard讀進數據且要等到(ctrl+d, ^d)結束輸入。
2.2 重定向輸出>n
(output redirection)
當你搞懂了0<
原來就是改變stdin
的數據輸入通道之後,
相信要理解如下兩個redirection就不難了:
1>
#改變stdout的輸出通道;2>
#改變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
都重定向輸出到文件了。
呵呵,看來要理解>
一點也不難啦是不? 沒騙你吧? ^_^
不過有些地方還是要注意一下的。
$ ls my.file no.such.file 1>file.both 2>file.both
假如stdout
(1)與stderr
(2)都同時在寫入file.both的話,
則是採取"覆蓋"的方式:後來寫入覆蓋前面的。
讓我們假設一個stdout
與stderr
同時寫入到file.out的情形好了;
- 首先
stdout
寫入10個字符 - 然後
stderr
寫入6個字符
那麼,這時原本的stdout
輸出的10個字符,
將被stderr
輸出的6個字符覆蓋掉了。
那如何解決呢?所謂山不轉路轉,路不轉人轉嘛,
我們可以換一個思維:
將stderr
導進stdout
或者將stdout
導進到stderr
,
而不是大家在搶同一份文件,不就行了。
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
這樣,不就皆大歡喜了嗎? ~~~ ^_^
不過,光解決了同時寫入的問題還不夠, 我們還有其他技巧需要了解的。 故事還沒有結束,別走開廣告後,我們在回來....
2.3 I/O重定向與linux中的/dev/null
okay,這次不講I/O Redirection, 請佛吧...
(有沒有搞錯?網中人
是否頭殼燒壞了?...)嘻~~~^_^
學佛的最高境界,就是"四大皆空"。 至於是空哪四大塊,我也不知,因為我還沒有到那個境界.. 這個“空”字,卻非常值得反覆把玩: ---色即是空,空即是色 好了,施主要是能夠領會"空"的禪意,那離修成正果不遠了。
在linux的文件系統中,有個設備文件: /dev/null
.
許多人都問過我,那是什麼玩意兒?
我跟你說好了,那就是"空"啦。
沒錯空空如也的空就是null了... 請問施主是否忽然有所頓悟了呢? 然則恭喜了。
這個null在 I/O Redirection中可有用的很呢?
- 將fd
1
跟fd2
重定向到/dev/null去,就可忽略stdout, stderr的輸出。 - 將fd
0
重定向到/dev/null,那就是讀進空(nothing).
比方說,我們在執行一個進程時,會同時輸出到stdout與stderr, 假如你不想看到stderr(也不想存到文件), 那就可以:
$ ls my.file no.such.file 2>/dev/null
my.file
若要相反:只想看到stderr呢? 還不簡單將stdout,重定向的/dev/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
Tips:
將&>換成>&也行!
2.4 重定向輸出append (>>
)
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
如此一來,被重定向的文件的之前的內容並不會丟失, 而新的內容則一直追加在最後面去。so easy?...
但是,只要你再次使用>
來重定向輸出的話,
那麼,原來文件的內容被truncated(清洗掉)。
這是,你要如何避免呢?
----備份, 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
留意到沒有:
在>
後面加個|
就好,
注意: >
與|
之間不能有空白哦...
2.5 I/O Redirection的優先級
呼....(深呼吸吐納一下吧)~~~ ^_^ 再來還有一個難題要你去參透呢:
$ 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是空的呢? why? why? why?
前面提到:$cat < file > file
之後,
原本有內容的文件,結果卻被清空了。
要理解這個現象其實不難,
這只是priority的問題而已:
在IO Redirection中, stdout與stderr的管道先準備好,
才會從stdin讀入數據。
也就是說,在上例中,>file
會將file清空,
然後才讀入 < file
。
但這時候文件的內容已被清空了,因此就變成了讀不進任何數據。
哦,~原來如此~^_^
那...如下兩例又如何呢?
$ cat <> file
$ cat < file >>file
嗯...同學們,這兩個答案就當練習題嘍, 下課前交作業。
Tips: 我們瞭解到
>file
能夠快速把文件file清空; 或者使用:>file
同樣可以清空文件,:>file
與>file
的功能: 若文件file存在,則將file清空; 否則,創建空文件file (等效於touch file
); 二者的差別在於>file
的方式不一定在所有的shell的都可用。
exec 5<>file; echo "abcd" >&5; cat <&5
將file文件的輸入、輸出定向到文件描述符5, 從而描述符5可以接管file的輸入輸出; 因此,cat <>file
等價於cat < file
。而
cat < file >>file
則使file內容成幾何級數增長。
好了, I/O Redirection也快講完了,
sorry,因為我也只知道這麼多而已啦~嘻~^_^
不過,還有一樣東東是一定要講的,各位觀眾(請自行配樂~!#@$%):
就是pipe line
也。
2.6 管道(pipe line)
談到pipe line
,我相信不少人都不會陌生:
我們在很多command line上常看到|
符號就是pipe line了。
不過,pipe line究竟是什麼東東呢? 別急別急...先查一下英文字典,看看pipe是什麼意思? 沒錯他就是“水管”的意思... 那麼,你能想象一下水管是怎樣一個根接一根的嗎? 又, 每根水管之間的input跟output又如何呢? 靈光一閃:原來pipe line的I/O跟水管的I/O是一模一樣的: 上一個命令的stdout接到下一個命令的stdin去了 的確如此。不管在command line上使用了多少個pipe line, 前後兩個command的I/O是彼此連接的 (恭喜:你終於開放了 ^_^ )
不過...然而...但是... ...stderr呢? 好問題不過也容易理解: 若水管漏水怎麼辦? 也就是說:在pipe line之間, 前一個命令的stderr是不會接進下一個命令的stdin的, 其輸出,若不用2>file的話,其輸出在monitor上來。 這點請你在pipe line運用上務必要注意的。
那,或許你有會問: 有辦法將stderr也喂進下一個命令的stdin嗎? (貪得無厭的傢伙),方法當然是有的,而且,你早已學習過了。 提示一下就好:**請問你如何將stderr合併進stdout一同輸出呢? 若你答不出來,下課後再來問我...(如果你臉皮足夠厚的話...)
或許,你仍意猶未盡,或許,你曾經碰到過下面的問題:
在cmd1 | cmd2 | cmd3 | ...
這段pipe line中如何將cmd2的輸出保存到一個文件呢?
若你寫成cmd1 | cmd2 >file | cmd3
的話,
那你肯定會發現cmd3
的stdin是空的,(當然了,你都將
水管接到別的水池了)
聰明的你或許會如此解決:
cmd1 | cmd2 >file; cmd3 < file
是的,你可以這樣做,但最大的壞處是: file I/O會變雙倍,在command執行的整個過程中, file I/O是最常見的最大效能殺手。 凡是有經驗的shell操作者,都會盡量避免或降低file I/O的頻度。
那上面問題還有更好的方法嗎?
有的,那就是tee
命令了。
所謂的tee
命令是在不影響原本I/O的情況下,
將stdout賦值到一個文件中去。
因此,上面的命令行,可以如此執行:
cmd1 | cmd2 | tee file | cmd3
在預設上,tee
會改寫目標文件,
若你要改為追加內容的話,那可用-a參數選項。
基本上,pipe line的應用在shell操作上是非常廣泛的。 尤其是在text filtering方面, 如,cat, more, head, tail, wc, expand, tr, grep, sed, awk...等等文字處理工具。 搭配起pipe line 來使用,你會覺得 command line 原來活得如此精彩的。 常讓人有“眾裡尋他千百度,驀然回首,那人卻在燈火闌珊處”之感...
好了,關於I/O Redirection的介紹就到此告一段落。 若日後,有空的話,在為大家介紹其他在shell上好玩的東西。