Zsh 開發指南(第十三篇 管道和重定向)

導讀

到目前為止,我們已經大致了解了 zsh 的語法特性,可以寫一些功能不復雜的腳本了。但 shell 腳本主要的應用場景并不是閉門造車寫獨立的程序,而是和外部環境交互。所以要寫出實用的腳本,要了解 zsh 如何和外部環境交互。這里的外部環境包括其他進程、文件系統、網絡等等。本篇主要講管道和重定向,這是和其他進程、文件系統等交互的基礎。

本文中的命令主要是為了演示管道的用法,在實際腳本中通常不需要使用這些命令,因為可以用 zsh 代碼直接實現。另外本系列文章不詳細講任何外部命令的用法,因為相關文檔或者書籍特別多。如果看不懂本文的某些內容,可以暫時跳過,基本不影響其余部分的理解。

管道

管道是類 Unix 系統中的一個比較基礎也特別重要的概念,它用于將一個程序的輸出作為另一個程序的輸入,進而兩個程序的數據可以互通。如果只是使用管道,還是非常簡單易懂的,并不需要了解管道的實現細節。

管道的基本用法:

% 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 的結果作為輸入讀取過來,然后統計行數,輸出結果。

關于管道的更多細節

我們再運行一個簡單的例子:

% 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 是指向偽終端設備文件的,進程就是通過這個來讀取用戶的輸入和輸出自己的內容。0 是標準輸入(即用戶輸入端),1 是標準輸出(即正常情況的輸出端),2 是錯誤輸出(即異常情況的輸出端)。但是 cat 的輸出端指向了 一個管道,并且 wc 的 輸入端指向了一個相同的管道,這代表兩個進程的輸入輸出端是通過管道連接的。這種管道是匿名管道,即只在內核中存在,是沒有對應的文件路徑的。

重定向

重定向,指的便是 fd 的重定向,管道也是重定向的一種方法。但用得更多的是將進程的 fd 重定向到文件。

一個最簡單的例子是輸出內容到文件。

% 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 命令。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 中的重定向還是非常靈活好用的,之后的文章會詳細講在實際場景中怎樣使用。

參考

http://adelphos.blog.51cto.com/2363901/1601563

更新歷史

20170901:增加“exec 命令的用法”。

本文不再更新,全系列文章在此更新維護:github.com/goreliu/zshguide

付費解決 Windows、Linux、Shell、C、C++、AHK、Python、JavaScript、Lua 等領域相關問題,靈活定價,歡迎咨詢,微信 ly50247。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 本文筆記源自這里——[實驗樓]歡迎大家在下面交流其中有問題的地方喜歡請點收藏,每日更新(全部已親自實踐). 一. ...
    東皇Amrzs閱讀 4,021評論 7 54
  • 一、實驗介紹 1.1 實驗內容 你可能對重定向這個概念感到些許陌生,但你應該在前面的課程中多次見過>或>>操作了,...
    harrytc閱讀 710評論 0 0
  • tr,tee 程序的IO 簡單的說程序由指令(命令)和數據(操作對象)組成,在linux上一切皆文件,程序操作對象...
    香吉矢閱讀 708評論 0 1
  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數據革命閱讀 12,198評論 2 33
  • 總是喜歡懷念過往,過往的人和事。好的壞的。。。!一直問自己是不是已經老了,這么懷念從前,總想回到過去。過去的一切似...
    如若錯過閱讀 116評論 0 0