python shell-like pipe

Posted by tjwei on 星期六, 4月 26, 2008 with No comments

像是 python generator tricks 一樣,其實不少人希望能在 python 裡面做 shell-like pipe。很明顯的,用 | operator 可以辦到這件事情,像是 ASPN Recipe: Shell-like data processing 就是一個例子。但缺點是要用一個 terminator 放在最後。此外,每個指令也都必須要加括號。所以,即使是簡單的 ls,也要變成 ls()|printlines 。而且,你要自己幫每個指令自己加上 __ror__。

PipeCmd

所以,我用三個方式來解決這些問題。最後的 __ror__,現在可以用 decorator 來處理了。terminator,我改用 __repr__ 來取代,這樣,在 python interactive shell 下,可以直接看到想要的回應。也因為如此,所有的資料都會被轉成 PipeOuput class (其實只是 iterator 加上 | 和 repr)。至於第一個問題,我用類似 partial function 的 class PiprCmd 來處理,有一個特別的 argument 稱為 stdin,用來代表 pipe input。

這樣,我們就可以
from pipeutils import *
ls|wc
ls|tail(n=5)|sort
cat/"*.py"|grep("def")
grep("pipe")/"*.py"/"/usr/include/*.h"
grep("^a","aaa\nbbb\nccc\nabc".splitlines(), open("text","rb"))
裡面的 / 是吃檔案名稱的,cat("text") 等於是 cat/"text",但是 cat/"text*" 等於 cat("text1", "text_something", ...)
更好的是,也可以和一般吃 iterator 的函數互相作用,也可以用一般 iterator 作用 (pmap 是把普通的的函數變成吃 iterator 的函數)
ls|(lambda g: (l.upper() for l in g))
"abcdefg"|pmap(mul,range(7))
import math
range(0,90,15)|pmap(lambda x:x*math.pi/180)|pmap(math.sin)
我的方法不算是最好的,應該只要用一個 class 就行。不過,這樣的解法很直覺。
其實用 ruby 會比 python 更像 shell,因為 ruby 函數呼叫可以不用括號。
另外把所有物件變成 file stream like 可能會更像 shell pipe的行為。
其實還挺多人想要把 python, ruby, perl 的強大功能跟 bash 這類 shell 混和。有一個 perl 的 project 就是把 unix 工具用 pure perl 實現。 python 也有很多,比方 shython 等等,python 本身也有 shutil, diff, 而 ipython 也有 shell-like 功能。
**Update: 20080426
改成只用一個 class pipecmd, 邏輯上比較簡潔一點。同時也做了一些處理,讓 cat(range(10))|sum 也可以跑。
**Update: 20080427
基本上一樣原理的 minipipe.py 只有 60行
Categories: