[python] 使用 python 呼叫 console 執行檔,一個通用的 recipe
用 python 呼叫 console 執行檔有什麼稀奇呢?不就 import os, os.system 就好了嗎?
對,就這麼簡單。
import os
os.system('net')
但是要在程式中知道執行的成功或失敗,甚至得到執行檔的輸出,又該如何呢?
最近挖了一個 guiminer 的 python 專案的程式碼來看,學到一些東西。
該專案使用 wxpython 當做 gui。正好是 gui 程式與執行檔的整合範例。
這範例有以下特點:
1. 使用 subprocess 是目前 python 建議方式
subprocess.Popen(cmd, cwd=cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
creationflags=flags,
shell=(sys.platform != 'win32'))
2. 通常希望不要新開視窗,上述的 flags 可以控制視窗形式。其值是如下方式取得
try: import win32process
except ImportError: flags = 0
else: flags = win32process.CREATE_NO_WINDOW
3. 為了要監控程式的輸出,把 stdout 及 stderr 導至 PIPE 及 STDOUT (如 1.) 如此程式便需要不斷地讀取 stdout。
4. 使用 regular expression 針對輸出獲得自己想要的結果。
5. 也因為執行外部程式時需不斷地讀取 stdout,必定要另開 thread 來處理。使用 threading.Event() 來控制 run() 裡面的 while 迴圈何時離開。
觀察到以上 5 點,接下來便是改寫他的程式成為通用的 recipe。因為不想跟 gui 套件綁在一起,所以就把 gui 相關的拿掉,整理之後成為如下的 class。
import threading
import re
import subprocess
import sysclass CmdThreadListener(threading.Thread):
'''
Assign cmd and cwd
'''LINES = []
def __init__(self, cmd, cwd, callback=None, unhandle_callback=None):
threading.Thread.__init__(self)
self.shutdown_event = threading.Event()
self.cmd = cmd
self.cwd = cwd
self.callback = callback
self.unhandle_callback = unhandle_callbackdef run(self):
try:
import win32process
except ImportError:
flags = 0
else:
flags = win32process.CREATE_NO_WINDOWsub_proc = subprocess.Popen(self.cmd, cwd=self.cwd,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
creationflags=flags,
shell=(sys.platform != 'win32'))self.proc = sub_proc
while not self.shutdown_event.is_set():
line = self.proc.stdout.readline().strip()
if not line:
continue
if len(self.LINES) == 0:
unhandleline = line
if self.unhandle_callback is not None:
self.unhandle_callback(unhandleline)for s, event_func in self.LINES:
match = re.search(s, line, flags=re.I)
if match is not None:
event = event_func(match)
if event is not None:
if self.callback is not None:
self.callback(event)
break
else:
unhandleline = line
if self.unhandle_callback is not None:
self.unhandle_callback(unhandleline)def stop(self):
if self.proc is not None and self.proc.returncode is None:
try:
self.proc.terminate()
except OSError:
pass
self.shutdown_event.set()
以下是它的使用方法:
import time
import osmodule_name = sys.executable if hasattr(sys, 'frozen') else __file__
abs_path = os.path.abspath(module_name)
cmd = 'net'
cwd = os.path.dirname(abs_path)print cmd
cmd = cmd.encode('cp950') # quick modify for windows console
def p(x):
print(x)listener_instance = CmdThreadListener(cmd, cwd, unhandle_callback=p)
listener_instance.daemon = True
listener_instance.start()time.sleep(5)
listener_instance.stop()
listener_instance.join()
它的輸出結果如下:
net
這個命令的語法是:
NET
[ ACCOUNTS | COMPUTER | CONFIG | CONTINUE | FILE | GROUP | HELP |
HELPMSG | LOCALGROUP | PAUSE | SESSION | SHARE | START |
STATISTICS | STOP | TIME | USE | USER | VIEW ]
在 windows 下,Popen 的 shell=False 時,cmd 不能夠是 shell 裡面的指令。所以 dir 之類的就不行用。若是想執行 shell 裡面的指令,Popen 的 shell 參數要設成 True。
註:若是照我的測試的程式碼放到 IDLE 直接執行,可能會出現 NameError: global name '__file__' is not defined。那是 IDLE 忘了 assign __file__ 的 bug。請到 cmd 底下執行 python 程式。