Zsh 開發指南(第十三篇 管道和重定向)
導讀
到目前為止,我們已經大致了解了 zsh 的語法特性,可以寫一些功能不複雜的腳本了。但 shell 腳本主要的應用場景並不是閉門造車寫獨立的程序,而是和外部環境交互。所以要寫出實用的腳本,要了解 zsh 如何和外部環境交互。這裡的外部環境包括其他進程、文件系統、網路等等。本篇主要講管道和重定向,這是和其他進程、文件系統等交互的基礎。
本文中的命令主要是為了演示管道的用法,在實際腳本中通常不需要使用這些命令,因為可以用 zsh 代碼直接實現。另外本系列文章不詳細講任何外部命令的用法,因為相關文檔或者書籍特別多。如果看不懂本文的某些內容,可以暫時跳過,基本不影響其餘部分的理解。
管道
管道是類 Unix 系統中的一個比較基礎也特別重要的概念,它用於將一個程序的輸出作為另一個程序的輸入,進而兩個程序的數據可以互通。如果只是使用管道,還是非常簡單易懂的,並不需要了解管道的實現細節。
管道的基本用法:
% lsgit tmp# wc -l 功能是計算輸入內容的行數% ls | wc -l2
| 即管道,在鍵盤上是主鍵盤區右側 對應的上檔鍵字元。如果只輸入 wc -l,wc 會等待用戶輸入,這時可以輸入字元串,然後回車繼續輸入,直到按 ctrl + d 結束輸入。然後 wc 會統計用戶一共輸入了多少行,然後輸出行數。
# 敲 wc -l 回車後,依次按 a 回車 b 回車 ctrl + d% wc -lab2
但如果前邊有個管道符號,ls | wc -l,那麼 wc 就不等待用戶輸入了,而是直接將 ls 的結果作為輸入讀取過來,然後統計行數,輸出結果。
關於管道的更多細節
我們再運行一個簡單的例子:
% cat | wc -l# 查看 cat 進程打開的 fd% ls -l /proc/$(pidof cat)/fdtotal 0lrwx------ 1 goreliu goreliu 0 2017-08-30 21:15 0 -> /dev/pts/1l-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)/fdtotal 0lr-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/1lrwx------ 1 goreliu goreliu 0 2017-08-30 21:16 2 -> /dev/pts/1
cat 命令的效果是等待用戶輸入,等用戶輸入一行,它就把這行再輸出來,直到用戶按 ctrl - d。所以 cat | wc -l 也會等待用戶輸入。
我們看下 fd 的指向,/dev/ps1/1 是指向偽終端設備文件的,進程就是通過這個來讀取用戶的輸入和輸出自己的內容。0 是標準輸入(即用戶輸入端),1 是標準輸出(即正常情況的輸出端),2 是錯誤輸出(即異常情況的輸出端)。但是 cat 的輸出端指向了 一個管道,並且 wc 的 輸入端指向了一個相同的管道,這代表兩個進程的輸入輸出端是通過管道連接的。這種管道是匿名管道,即只在內核中存在,是沒有對應的文件路徑的。
重定向
重定向,指的便是 fd 的重定向,管道也是重定向的一種方法。但用得更多的是將進程的 fd 重定向到文件。
一個最簡單的例子是輸出內容到文件。
% echo abce > test.txt% cat test.txtabce
因為這個用法太常見了,大家可能習以為常了。我們依然來看下更多的細節。
% cat > test.txt# 在另一個 zsh 中運行% ls -l /proc/$(pidof cat)/fdtotal 0lrwx------ 1 goreliu goreliu 0 Aug 30 21:43 0 -> /dev/pts/1l-wx------ 1 goreliu goreliu 0 Aug 30 21:43 1 -> /tmp/test.txtlrwx------ 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)/fdtotal 0lr-x------ 1 goreliu goreliu 0 Aug 30 21:46 0 -> /tmp/0.txtl-wx------ 1 goreliu goreliu 0 Aug 30 21:46 1 -> /tmp/1.txtl-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/fdtotal 0lrwx------ 1 goreliu goreliu 0 Aug 30 21:57 0 -> /dev/pts/1l-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/fdtotal 0l-wx------ 1 goreliu goreliu 0 Aug 30 21:58 12 -> /tmp/0.txtl-wx------ 1 goreliu goreliu 0 Aug 30 21:58 13 -> /tmp/1.txtlr-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.txtgoodgoodgood
給 cat 的標準輸出重定向 3 個文件,它將 3 個文件的內容全部讀取了出來。
除了能同時重定向 fd 到多個文件外,還可以同時重定向到管道和文件。
# 敲完 a b c 後 ctrl -d 退出% cat >0.txt >1.txt | wc -labc3% cat 0.txt 1.txtabcabc
可以看到輸入的內容寫入了文件,並且通過管道傳給了 wc -l,不用說,這又是 zsh 在做背後工作,將數據分發給了文件和管道。所以在 zsh 中是不需要使用 tee 命令的。
命名管道
除了匿名管道,我們還可以使用命名管道,這樣更容易控制。命名管道所使用的文件即 FIFO(First Input First Output,先入先出)文件。
# mkfifo 用來創建 FIFO 文件% mkfifo fifo% ls -lprw-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)/fdtotal 0lrwx------ 1 goreliu goreliu 0 Aug 30 21:35 0 -> /dev/pts/2l-wx------ 1 goreliu goreliu 0 Aug 30 21:35 1 -> /tmp/fifolrwx------ 1 goreliu goreliu 0 Aug 30 21:35 2 -> /dev/pts/2% ls -l /proc/$(pidof wc)/fdtotal 0lr-x------ 1 goreliu goreliu 0 Aug 30 21:34 0 -> /tmp/fifolrwx------ 1 goreliu goreliu 0 Aug 30 21:34 1 -> /dev/pts/1lrwx------ 1 goreliu goreliu 0 Aug 30 21:34 2 -> /dev/pts/1
可以看到之前的匿名管道已經變成了我們剛剛創建的 fifo 文件,其他的並無不同。
總結
本文講了管道和重定向的基本概念和各種用法。Zsh 中的重定向還是非常靈活好用的,之後的文章會詳細講在實際場景中怎樣使用。
本文不再更新,全系列文章在此更新維護:github.com/goreliu/zshguide
付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活定價,歡迎諮詢,微信 ly50247。
推薦閱讀: