導讀
到目前為止,我們已經(jīng)大致了解了 zsh 的語法特性缠劝,可以寫一些功能不復(fù)雜的腳本了妖泄。但 shell 腳本主要的應(yīng)用場景并不是閉門造車寫?yīng)毩⒌某绦颍呛屯獠凯h(huán)境交互客税。所以要寫出實用的腳本况褪,要了解 zsh 如何和外部環(huán)境交互。這里的外部環(huán)境包括其他進程更耻、文件系統(tǒng)测垛、網(wǎng)絡(luò)等等。本篇主要講管道和重定向秧均,這是和其他進程食侮、文件系統(tǒng)等交互的基礎(chǔ)号涯。
本文中的命令主要是為了演示管道的用法,在實際腳本中通常不需要使用這些命令锯七,因為可以用 zsh 代碼直接實現(xiàn)链快。另外本系列文章不詳細講任何外部命令的用法,因為相關(guān)文檔或者書籍特別多眉尸。如果看不懂本文的某些內(nèi)容域蜗,可以暫時跳過,基本不影響其余部分的理解噪猾。
管道
管道是類 Unix 系統(tǒng)中的一個比較基礎(chǔ)也特別重要的概念地消,它用于將一個程序的輸出作為另一個程序的輸入,進而兩個程序的數(shù)據(jù)可以互通畏妖。如果只是使用管道脉执,還是非常簡單易懂的,并不需要了解管道的實現(xiàn)細節(jié)戒劫。
管道的基本用法:
% ls
git tmp
# wc -l 功能是計算輸入內(nèi)容的行數(shù)
% ls | wc -l
2
| 即管道半夷,在鍵盤上是主鍵盤區(qū)右側(cè) \ 對應(yīng)的上檔鍵字符。如果只輸入 wc -l迅细,wc 會等待用戶輸入巫橄,這時可以輸入字符串,然后回車繼續(xù)輸入茵典,直到按 ctrl + d 結(jié)束輸入湘换。然后 wc 會統(tǒng)計用戶一共輸入了多少行,然后輸出行數(shù)统阿。
# 敲 wc -l 回車后彩倚,依次按 a 回車 b 回車 ctrl + d
% wc -l
a
b
2
但如果前邊有個管道符號,ls | wc -l扶平,那么 wc 就不等待用戶輸入了帆离,而是直接將 ls 的結(jié)果作為輸入讀取過來,然后統(tǒng)計行數(shù)结澄,輸出結(jié)果哥谷。
關(guān)于管道的更多細節(jié)
我們再運行一個簡單的例子:
% cat | wc -l
# 查看 cat 進程打開的 fd
% ls -l /proc/$(pidof cat)/fd
total 0
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:15 0 -> /dev/pts/1
l-wx------ 1 goreliu goreliu 0 2017-08-30 21:15 1 -> pipe:[2803]
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:15 2 -> /dev/pts/1
# 查看 wc 進程打開的 fd
% ls -l /proc/$(pidof wc)/fd
total 0
lr-x------ 1 goreliu goreliu 0 2017-08-30 21:16 0 -> pipe:[2803]
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 1 -> /dev/pts/1
lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 2 -> /dev/pts/1
cat 命令的效果是等待用戶輸入,等用戶輸入一行麻献,它就把這行再輸出來们妥,直到用戶按 ctrl - d。所以 cat | wc -l 也會等待用戶輸入勉吻。
我們看下 fd 的指向监婶,/dev/ps1/1 是指向偽終端設(shè)備文件的,進程就是通過這個來讀取用戶的輸入和輸出自己的內(nèi)容餐曼。0 是標準輸入(即用戶輸入端)压储,1 是標準輸出(即正常情況的輸出端)鲜漩,2 是錯誤輸出(即異常情況的輸出端)源譬。但是 cat 的輸出端指向了 一個管道集惋,并且 wc 的 輸入端指向了一個相同的管道,這代表兩個進程的輸入輸出端是通過管道連接的踩娘。這種管道是匿名管道刮刑,即只在內(nèi)核中存在,是沒有對應(yīng)的文件路徑的养渴。
重定向
重定向雷绢,指的便是 fd 的重定向,管道也是重定向的一種方法理卑。但用得更多的是將進程的 fd 重定向到文件翘紊。
一個最簡單的例子是輸出內(nèi)容到文件。
% echo abce > test.txt
% cat test.txt
abce
因為這個用法太常見了藐唠,大家可能習以為常了帆疟。我們依然來看下更多的細節(jié)。
% cat > test.txt
# 在另一個 zsh 中運行
% ls -l /proc/$(pidof cat)/fd
total 0
lrwx------ 1 goreliu goreliu 0 Aug 30 21:43 0 -> /dev/pts/1
l-wx------ 1 goreliu goreliu 0 Aug 30 21:43 1 -> /tmp/test.txt
lrwx------ 1 goreliu goreliu 0 Aug 30 21:43 2 -> /dev/pts/1
可以看到標準輸出已經(jīng)指向 test.txt 文件了宇立。
除了標準輸出可以重定向踪宠,標準輸入(fd 0),錯誤輸出(fd 2)也都可以妈嘹。
% touch 0.txt 1.txt 2.txt
% sleep 1000 <0.txt >1.txt 2>2.txt
# 在另一個 zsh 中運行
% ls -l /proc/$(pidof sleep)/fd
total 0
lr-x------ 1 goreliu goreliu 0 Aug 30 21:46 0 -> /tmp/0.txt
l-wx------ 1 goreliu goreliu 0 Aug 30 21:46 1 -> /tmp/1.txt
l-wx------ 1 goreliu goreliu 0 Aug 30 21:46 2 -> /tmp/2.txt
<0.txt 是重定向標準輸入柳琢,2>2.txt 是重定向錯誤輸出,>1.txt(即 1>1.txt)是重定向到標準輸出润脸。然后我們看到 3 個文件已經(jīng)各就各位柬脸,全部被重定向了。但因為 sleep 并不去讀寫任何東西毙驯,重定向它的輸入輸出沒有什么意義肖粮。
更多重定向的用法
一個 fd 只能重定向到一個文件,一一對應(yīng)尔苦。但在 zsh 中涩馆,我們可以把一個 fd 對應(yīng)到多個文件。
% cat >0.txt >1.txt >2.txt
輸入完成后允坚,3 個文件的內(nèi)容都更新了魂那,這是怎么回事呢?
其實是 zsh 進程做了中介稠项。
% pstree -p | grep cat
`-tmux: server(1172)-+-zsh(1173)---cat(1307)---zsh(1308)
% ls -l /proc/1307/fd
total 0
lrwx------ 1 goreliu goreliu 0 Aug 30 21:57 0 -> /dev/pts/1
l-wx------ 1 goreliu goreliu 0 Aug 30 21:57 1 -> pipe:[2975]
lrwx------ 1 goreliu goreliu 0 Aug 30 21:57 2 -> /dev/pts/1
% ls -l /proc/1308/fd
total 0
l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 12 -> /tmp/0.txt
l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 13 -> /tmp/1.txt
lr-x------ 1 goreliu goreliu 0 Aug 30 21:58 14 -> pipe:[2975]
l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 15 -> /tmp/2.txt
可以看到 cat 的標準輸出是重定向到管道了涯雅,管道對面是 zsh 進程,然后 zsh 打開了那三個文件展运。實際將內(nèi)容寫入文件的是 zsh活逆,而不是 cat精刷。但不管是誰寫入的,這個用法很方便蔗候。
標準輸入怒允、錯誤輸出也可以重定向多個文件。
% echo good >0.txt >1.txt >2.txt
% cat <0.txt <1.txt <2.txt
good
good
good
給 cat 的標準輸出重定向 3 個文件锈遥,它將 3 個文件的內(nèi)容全部讀取了出來纫事。
除了能同時重定向 fd 到多個文件外,還可以同時重定向到管道和文件所灸。
# 敲完 a b c 后 ctrl -d 退出
% cat >0.txt >1.txt | wc -l
a
b
c
3
% cat 0.txt 1.txt
a
b
c
a
b
c
可以看到輸入的內(nèi)容寫入了文件丽惶,并且通過管道傳給了 wc -l,不用說爬立,這又是 zsh 在做背后工作钾唬,將數(shù)據(jù)分發(fā)給了文件和管道。所以在 zsh 中是不需要使用 tee 命令的侠驯。
命名管道
除了匿名管道抡秆,我們還可以使用命名管道,這樣更容易控制陵霉。命名管道所使用的文件即 FIFO(First Input First Output琅轧,先入先出)文件。
# mkfifo 用來創(chuàng)建 FIFO 文件
% mkfifo fifo
% ls -l
prw-r--r-- 1 goreliu goreliu 0 2017-08-30 21:29 fifo|
# cat 寫入 fifo
% cat > fifo
# 打開另一個 zsh踊挠,運行 wc -l 讀取 fifo
% wc -l < fifo
然后在 cat 那邊輸入一些內(nèi)容乍桂,按 ctrl - d 退出,wc 這邊就會統(tǒng)計輸入的行數(shù)效床。
在輸入完成之前睹酌,我們也可以看一下 cat 和 wc 兩個進程的 fd 指向哪里:
% ls -l /proc/$(pidof cat)/fd
total 0
lrwx------ 1 goreliu goreliu 0 Aug 30 21:35 0 -> /dev/pts/2
l-wx------ 1 goreliu goreliu 0 Aug 30 21:35 1 -> /tmp/fifo
lrwx------ 1 goreliu goreliu 0 Aug 30 21:35 2 -> /dev/pts/2
% ls -l /proc/$(pidof wc)/fd
total 0
lr-x------ 1 goreliu goreliu 0 Aug 30 21:34 0 -> /tmp/fifo
lrwx------ 1 goreliu goreliu 0 Aug 30 21:34 1 -> /dev/pts/1
lrwx------ 1 goreliu goreliu 0 Aug 30 21:34 2 -> /dev/pts/1
可以看到之前的匿名管道已經(jīng)變成了我們剛剛創(chuàng)建的 fifo 文件,其他的并無不同剩檀。
exec 命令的用法
說起重定向憋沿,就不得不提 exec 命令。exec 命令主要用于啟動新進程替換當前進程以及對 fd 做一些操作沪猴。
用 exec 啟動新進程:
% exec cat
看上去效果和直接運行 cat 差不多辐啄。但如果運行 ctrl + d 退出 cat,終端模擬器就關(guān)閉了运嗜,因為在運行 exec cat 的時候壶辜,zsh 進程將已經(jīng)被 cat 取代了,回不去了担租。
但在腳本中很少直接這樣使用 exec砸民,更多情況是用它來操作 fd:
# 將當前 zsh 的錯誤輸出重定向到 test.txt
% exec 2>test.txt
# 隨意敲入一個不存在的命令,錯誤提示不出現(xiàn)了
% fdsafds
# 錯誤提示被重定向到 test.txt 里
% cat test.txt
zsh: command not found: fdsafds
更多用法:
用法 | 功能 |
---|---|
n>filename | 重定向 fd n 的輸出到 filename 文件 |
n<filename | 重定向 fd n 的輸入為 filename 文件 |
n<>filename | 同時重定向 fd n 的輸入輸出為 filename 文件 |
n>&m | 重定向 fd n 的輸出到 fd m |
n<&m | 重定向 fd n 的輸入為 fd m |
n>&- | 關(guān)閉 fd n 的輸出 |
n<&- | 關(guān)閉 fd n 的輸入 |
更多例子:
# 把錯誤輸出關(guān)閉,這樣錯誤內(nèi)容就不再顯示
% exec 2>&-
% fsdafdsa
% exec 3>test.txt
% echo good >&3
% exec 3>&-
# 關(guān)閉后無法再輸出
% echo good >&3
zsh: 3: bad file descriptor
% exec 3>test.txt
# 將 fd 4 的輸出重定向到 fd 3
% exec 4>&3
% echo abcd >&4
# 輸出內(nèi)容到 fd 4岭参,test.txt 內(nèi)容更新了
% cat test.txt
abcd
通常情況我們用 exec 主要為了重定向輸出和關(guān)閉輸出反惕,比較少操作輸入。
總結(jié)
本文講了管道和重定向的基本概念和各種用法演侯。Zsh 中的重定向還是非常靈活好用的姿染,之后的文章會詳細講在實際場景中怎樣使用。
參考
http://adelphos.blog.51cto.com/2363901/1601563
更新歷史
20170901:增加“exec 命令的用法”蚌本。
本文不再更新盔粹,全系列文章在此更新維護:github.com/goreliu/zshguide
付費解決 Windows隘梨、Linux程癌、Shell、C轴猎、C++嵌莉、AHK、Python捻脖、JavaScript锐峭、Lua 等領(lǐng)域相關(guān)問題,靈活定價可婶,歡迎咨詢沿癞,微信 ly50247。