有一些現成的庫, 比如WExpect, 是開源的, 在github上可以搜尋到. 但是, 不知道為什麼, 在我自己的筆記本上不能正常工作. 而其原始碼也比較多, 懶得定位了.
於是自己實現了一個, 用法如下.
- 啟動和停止命令列
import my_cmd as cmd
cmd.start()
cmd.stop()
- prompt命令列提示符匹配字串
import my_cmd as cmd
print(cmd.get_prompt()) # 輸出: `[A-Za-z]:\\.*>$`
# cmd.set_prompt('>') # 設定提示符匹配字串, 需要是正規表示式
- 輪詢等待expect預期輸出
功能: 輪詢等待expect預期輸出, 如果沒有預期輸出, 則一直等待下去.
原型:expect(*regex_infos:str)->int:
expect()
: 如果不用引數, 則預設等待prompt提示符出現.
如果引數有一個或者多個正規表示式, 則返回第一個匹配的正規表示式在列表中的索引. 如果匹配到prompt, 則返回0.
import my_cmd as cmd
cmd.start()
cmd.expect()
cmd.stop()
輸出:
Microsoft Windows [版本 10.0.22621.3155]
(c) Microsoft Corporation。保留所有權利。
d:\MyCode\Libs\Python\Test\my_cmd>
cmd.exe process exited.
- 檢視和清除輸出
import my_cmd as cmd
cmd.start()
cmd.expect()
print(cmd.get_output()) # 獲取所有的輸出
print(cmd.get_last_line()) # 獲取最後一行的輸出
print(cmd.clear_output()) # 清除輸出資訊
cmd.stop()
- 輸入命令
write_line(cmd:str): 輸入命令, 自動新增換行符. 為防止輸出資訊干擾, 會先清除之前的輸出資訊.
import my_cmd as cmd
cmd.start()
cmd.expect()
cmd.write_line('pwd')
cmd.expect()
cmd.stop()
輸出:
Microsoft Windows [版本 10.0.22621.3155]
(c) Microsoft Corporation。保留所有權利。
d:\MyCode\Libs\Python\Test\my_cmd>pwd
/d/MyCode/Libs/Python/Test/my_cmd
d:\MyCode\Libs\Python\Test\my_cmd>
cmd.exe process exited.
原始碼
import Common
import io
import re
import time
import subprocess
# region 全域性變數
_buffer = io.StringIO()
'''存放輸出字串的緩衝區'''
_last_line_buffer = io.StringIO()
'''最後一行的輸出緩衝區. 主要是為了提高expect函式的匹配效率.'''
_proc:subprocess.Popen = None
'''cmd.exe程序物件'''
_prompt:str = r'[A-Za-z]:\\.*>$' # 有時候磁碟機代號也可能是小寫
r'''預設提示資訊. 形如: C:\Users\huzho>'''
# endregion
def clear_output() -> None:
'''清除輸出'''
global _buffer,_last_line_buffer
_buffer.close()
_buffer = io.StringIO()
_last_line_buffer.close()
_last_line_buffer = io.StringIO()
def expect(*regex_infos:str)->int:
'''
等待預期的字串出現. 為提高效率, 預設只匹配輸出的最後一行.
返回值: 匹配到的預期字串的索引, 如果為0, 表示prompt, 其餘的按順序為1,2,3...
'''
regex_infos = list(regex_infos)
regex_infos.insert(0, _prompt) # 增加上提示符
while True: # 迴圈匹配, 直到匹配成功為止
for i,reg_info in enumerate(regex_infos):
if re.search(reg_info, get_last_line()):
return i
time.sleep(0.1) # 防止死迴圈
def get_last_line() -> str:
'''獲取輸出的最後一行字串'''
return _last_line_buffer.getvalue()
def get_output() -> str:
'''獲取當前的輸出'''
return _buffer.getvalue()
def get_prompt() -> str:
'''獲取當前的提示符'''
return _prompt
@Common.run_in_thread
def _output():
'''輪詢輸出'''
global _last_line_buffer
while True:
output = _proc.stdout.read(1) # 讀取一個字元, 保證了只要有輸出, 就會及時響應
if output == '' and _proc.poll() is not None: # poll函式有值, 表示程序已退出
_buffer.close() # 清空緩衝區
_last_line_buffer.close()
print('cmd.exe process exited.')
break
print(output,end='')
_buffer.write(output) # 寫入緩衝區中
# 處理最後一行的緩衝區
if output == '\n': # 如果換行, 則重置最後一行
_last_line_buffer.close()
_last_line_buffer = io.StringIO()
else:
_last_line_buffer.write(output)
def set_prompt(prompt:str) -> None:
'''設定提示符'''
global _prompt
_prompt = prompt
def start():
'''啟動cmd程序並輪詢輸出'''
global _proc,_buffer,_last_line_buffer
_buffer = io.StringIO()
_last_line_buffer = io.StringIO()
_proc = subprocess.Popen('cmd.exe',
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
#stderr=subprocess.PIPE,
stderr=subprocess.STDOUT, # 錯誤資訊也同時輸出
#encoding='gbk', # windows下的編碼, 可以列印漢字資訊, 是不是`text=True`就可以了?
cwd=None, # 工作目錄, 後續可以增加設定工作目錄的功能
bufsize=1,
text=True)
_output() # 在另一個執行緒中輪詢輸出
def stop():
'''停止cmd程序'''
#send('exit')
_proc.terminate()
def write_line(line:str) -> None:
'''寫入命令. 寫入命令之前先清除之前的輸出. 以免有干擾.'''
clear_output() # 先清除之前的輸出
_proc.stdin.write(line + '\n')
_proc.stdin.flush()
BTW: 雖然主要是針對windows系統的, 但是改造一下, 應該也可以用於linux系統, 因為是純python語言開發的.