一篇技術文章若是僅僅是理論上講得天花亂墜,卻不能本身擼出東西來,那麼它寫的再好,也只能算紙上談兵。繼上一篇 《深刻 Shell 管道符的內部原理》 收到大量讀者粉絲的點贊以後,本篇咱們本身來實現一下管道符的功能。好比咱們將支持下面的複雜指令,有不少個管套符串起來的一系列指令。python
$ cmd1 | cmd2 | cmd3 | cmd4 | cmd5
複製代碼
咱們要使用 Python 語言,由於 Go 和 Java 語言都不支持 fork 函數。咱們最終須要的是下面這張圖,這張圖很簡單,可是爲了構造出這張圖,是須要費一番功夫的。shell
程序的代碼文件名是 pipe.py,程序的運行形式以下數組
python pipe.py "cat pipe.py | grep def | wc -l"
複製代碼
統計 pipe.py 文件代碼中包含 def 單詞的個數,輸出bash
3
複製代碼
每一條指令的運行都須要至少攜帶一個管道,左邊的管道或者右邊的管道。第一個指令和最後一個指令只有一個管道,中間的指令有兩個管道。管道的標識是它的一對讀寫描述符(r, w)。微信
左邊管道的讀描述符 left_pipe[0] 對接進程的標準輸入。右邊管道的寫描述符 right_pipe[1] 對接進程的標準輸出。調整完描述符後,就可使用 exec 函數來執行指令。函數
def run_cmd(cmd, left_pipe, right_pipe):
if left_pipe:
os.dup2(left_pipe[0], sys.stdin.fileno())
os.close(left_pipe[0])
os.close(left_pipe[1])
if right_pipe:
os.dup2(right_pipe[1], sys.stdout.fileno())
os.close(right_pipe[0])
os.close(right_pipe[1])
# 分割指令參數
args = [arg.strip() for arg in cmd.split()]
args = [arg for arg in args if arg]
try:
# 傳入指令名稱、指令參數數組
# 指令參數數組的第一個參數就是指令名稱
os.execvp(args[0], args)
except OSError as ex:
print "exec error:", ex
複製代碼
shell 須要運行多個進程,就必須用到 fork 函數來建立子進程,而後使用子進程來執行指令。post
子又生孫,孫又生子,子子孫孫無窮盡也。理論上使用管道能夠串接很是多的進程輸入輸出流。# 指令的列表以及下一條指令左邊的管道做爲參數
def run_cmds(cmds, left_pipe):
# 取出指令串的第一個指令,即將執行這第一個指令
cur_cmd = cmds[0]
other_cmds = cmds[1:]
# 建立管道
pipe_fds = ()
if other_cmds:
pipe_fds = os.pipe()
# 建立子進程
pid = os.fork()
if pid < 0:
print "fork process failed"
return
if pid > 0:
# 父進程來執行指令
# 同時傳入左邊和右邊的管道(可能爲空)
run_cmd(cur_cmd, left_pipe, pipe_fds)
elif other_cmds:
# 莫忘記關閉再也不使用的描述符
if left_pipe:
os.close(left_pipe[0])
os.close(left_pipe[1])
# 子進程遞歸繼續執行後續指令,攜帶新建立的管道
run_cmds(other_cmds, pipe_fds)
複製代碼
須要對命令行參數按豎線進行分割得出多條指令,開始進入遞歸執行ui
def main(cmdtext):
cmds = [cmd.strip() for cmd in cmdtext.split("|")]
# 第一條指令左邊沒有管道
run_cmds(cmds, ())
if __name__ == '__main__':
main(argv[1])
複製代碼
由於例子中的幾條指令執行時間過短,沒法經過 ps 命令來觀察進程關係。因此咱們在代碼里加了一句調試用的輸出代碼,輸出當前進程執行的指令名稱、進程號和父進程號。spa
def run_cmd(cmd, left_pipe, right_pipe):
print cmd, os.getpid(), os.getppid()
...
複製代碼
運行腳本時觀察輸出命令行
$ python pipe.py "cat pipe.py | grep def | wc -l"
cat pipe.py 49782 4503
grep def 49783 49782
wc -l 49784 49783
3
複製代碼
從輸出中能夠明顯看出父子進程的關係,第 N 條指令進程是第 N+1 條指令進程的父進程。在 run_cmds 函數中,fork 出子進程後由父進程來負責執行當前指令,剩餘的指令交給子進程執行。因此才造成了上面的進程關係。讀者能夠嘗試調整交互執行順序,讓子進程負責執行當前指令,而後再觀察輸出
$ python pipe.py "cat pipe.py | grep def | wc -l"
cat pipe.py 49949 49948
grep def 49950 49948
wc -l 49951 49948
3
複製代碼
你會發現這三個指令進程都共享同一個父進程,這個父進程就是 Python 進程。如上圖所示,咱們平時使用的 shell 在執行指令的時候造成的進程關係都是這種形式的,這種形式在邏輯結構上看起來更加清晰。
須要上面的完整源代碼,請關注下面的公衆號,在裏面回覆「管道」便可獲得源碼。
閱讀更多深度技術文章,掃一掃上面的二維碼關注微信公衆號「碼洞」