到目前爲止,咱們已經大體瞭解了 zsh 的語法特性,能夠寫一些功能不復雜的腳本了。但 shell 腳本主要的應用場景並非閉門造車寫獨立的程序,而是和外部環境交互。因此要寫出實用的腳本,要了解 zsh 如何和外部環境交互。這裏的外部環境包括其餘進程、文件系統、網絡等等。本篇主要講管道和重定向,這是和其餘進程、文件系統等交互的基礎。git
本文中的命令主要是爲了演示管道的用法,在實際腳本中一般不須要使用這些命令,由於能夠用 zsh 代碼直接實現。另外本系列文章不詳細講任何外部命令的用法,由於相關文檔或者書籍特別多。若是看不懂本文的某些內容,能夠暫時跳過,基本不影響其他部分的理解。github
管道是類 Unix 系統中的一個比較基礎也特別重要的概念,它用於將一個程序的輸出做爲另外一個程序的輸入,進而兩個程序的數據能夠互通。若是隻是使用管道,仍是很是簡單易懂的,並不須要瞭解管道的實現細節。shell
管道的基本用法:bash
% ls
git tmp
# wc -l 功能是計算輸入內容的行數
% ls | wc -l
2複製代碼
| 即管道,在鍵盤上是主鍵盤區右側 \ 對應的上檔鍵字符。若是隻輸入 wc -l,wc 會等待用戶輸入,這時能夠輸入字符串,而後回車繼續輸入,直到按 ctrl + d 結束輸入。而後 wc 會統計用戶一共輸入了多少行,而後輸出行數。微信
# 敲 wc -l 回車後,依次按 a 回車 b 回車 ctrl + d
% wc -l
a
b
2複製代碼
但若是前邊有個管道符號,ls | wc -l,那麼 wc 就不等待用戶輸入了,而是直接將 ls 的結果做爲輸入讀取過來,而後統計行數,輸出結果。網絡
咱們再運行一個簡單的例子:ide
% 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 也會等待用戶輸入。ui
咱們看下 fd 的指向,/dev/ps1/1 是指向僞終端設備文件的,進程就是經過這個來讀取用戶的輸入和輸出本身的內容。0 是標準輸入(即用戶輸入端),1 是標準輸出(即正常狀況的輸出端),2 是錯誤輸出(即異常狀況的輸出端)。可是 cat 的輸出端指向了 一個管道,而且 wc 的 輸入端指向了一個相同的管道,這表明兩個進程的輸入輸出端是經過管道鏈接的。這種管道是匿名管道,即只在內核中存在,是沒有對應的文件路徑的。spa
重定向,指的即是 fd 的重定向,管道也是重定向的一種方法。但用得更多的是將進程的 fd 重定向到文件。code
一個最簡單的例子是輸出內容到文件。
% echo abce > test.txt
% cat test.txt
abce複製代碼
由於這個用法太常見了,你們可能習覺得常了。咱們依然來看下更多的細節。
% 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複製代碼
能夠看到標準輸出已經指向 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 個文件已經各就各位,所有被重定向了。但由於 sleep 並不去讀寫任何東西,重定向它的輸入輸出沒有什麼意義。
一個 fd 只能重定向到一個文件,一一對應。但在 zsh 中,咱們能夠把一個 fd 對應到多個文件。
% cat >0.txt >1.txt >2.txt複製代碼
輸入完成後,3 個文件的內容都更新了,這是怎麼回事呢?
實際上是 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 打開了那三個文件。實際將內容寫入文件的是 zsh,而不是 cat。但不論是誰寫入的,這個用法很方便。
標準輸入、錯誤輸出也能夠重定向多個文件。
% echo good >0.txt >1.txt >2.txt
% cat <0.txt <1.txt <2.txt
good
good
good複製代碼
給 cat 的標準輸出重定向 3 個文件,它將 3 個文件的內容所有讀取了出來。
除了能同時重定向 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複製代碼
能夠看到輸入的內容寫入了文件,而且經過管道傳給了 wc -l,不用說,這又是 zsh 在作背後工做,將數據分發給了文件和管道。因此在 zsh 中是不須要使用 tee 命令的。
除了匿名管道,咱們還可使用命名管道,這樣更容易控制。命名管道所使用的文件即 FIFO(First Input First Output,先入先出)文件。
# mkfifo 用來建立 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 那邊輸入一些內容,按 ctrl - d 退出,wc 這邊就會統計輸入的行數。
在輸入完成以前,咱們也能夠看一下 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複製代碼
能夠看到以前的匿名管道已經變成了咱們剛剛建立的 fifo 文件,其餘的並沒有不一樣。
提及重定向,就不得不提 exec 命令。exec 命令主要用於啓動新進程替換當前進程以及對 fd 作一些操做。
用 exec 啓動新進程:
% exec cat複製代碼
看上去效果和直接運行 cat 差很少。但若是運行 ctrl + d 退出 cat,終端模擬器就關閉了,由於在運行 exec cat 的時候,zsh 進程將已經被 cat 取代了,回不去了。
但在腳本中不多直接這樣使用 exec,更多狀況是用它來操做 fd:
# 將當前 zsh 的錯誤輸出重定向到 test.txt
% exec 2>test.txt
# 隨意敲入一個不存在的命令,錯誤提示不出現了
% 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>&- | 關閉 fd n 的輸出 |
n<&- | 關閉 fd n 的輸入 |
更多例子:
# 把錯誤輸出關閉,這樣錯誤內容就再也不顯示
% exec 2>&-
% fsdafdsa
% exec 3>test.txt
% echo good >&3
% exec 3>&-
# 關閉後沒法再輸出
% echo good >&3
zsh: 3: bad file descriptor
% exec 3>test.txt
# 將 fd 4 的輸出重定向到 fd 3
% exec 4>&3
% echo abcd >&4
# 輸出內容到 fd 4,test.txt 內容更新了
% cat test.txt
abcd複製代碼
一般狀況咱們用 exec 主要爲了重定向輸出和關閉輸出,比較少操做輸入。
本文講了管道和重定向的基本概念和各類用法。Zsh 中的重定向仍是很是靈活好用的,以後的文章會詳細講在實際場景中怎樣使用。
adelphos.blog.51cto.com/2363901/160…
全系列文章地址:github.com/goreliu/zsh…
付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活訂價,歡迎諮詢,微信 ly50247。