python控制windows命令行程序

有一些现成的库, 比如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语言开发的.

posted @ 2024-03-06 20:19  顺其自然,道法自然  阅读(44)  评论(0编辑  收藏  举报