3、第一个自动化脚本:使用subprocess运行单个仿真命令,捕获输出与错误。

好,咱们正式开始写第一个自动化脚本。

说实话,很多新手一上来就想搞个「万能框架」。我个人习惯是——先跑通一条命令,再谈批量。你想想看,连单个仿真都抓不住输出,批量跑一百个又有什么意义?

3.1 为什么是subprocess?

Python里执行系统命令,主流就三个选择:os.system、os.popen、subprocess。

我直接说结论:用subprocess。为什么?

  • os.system 只能拿到退出码,拿不到输出内容。你连仿真报了什么错都不知道。
  • os.popen 虽然能读输出,但控制能力弱,容易卡死。
  • subprocess 是亲儿子,功能全,坑少。

我在项目中遇到过有人用 os.system 跑仿真,结果仿真挂了三天三夜没人发现——因为脚本只看退出码是否为0,根本没捕获stderr里的「Fatal Error」。

核心原则: 永远用 subprocess 管理外部进程。这是工业级脚本的底线。

3.2 最简版本:跑一条命令

先看一个最基础的例子。假设我们要跑一条VCS仿真命令:

import subprocess

cmd = "vcs -R -f test.f +v2k"
result = subprocess.run(cmd, shell=True)
print("返回码:", result.returncode)

嗯,这里要注意:shell=True 表示通过系统shell执行。好处是你可以直接用管道、通配符。坏处是——有安全风险。如果你拼接用户输入的命令,小心注入攻击。

我个人习惯:测试脚本里用 shell=True 没问题,因为命令是写死的。但如果是Web服务调仿真,必须用 shell=False + 列表传参。

3.3 捕获输出:stdout和stderr

光看返回码不够。仿真失败了,你得知道哪里失败。捕获输出才是关键。

import subprocess

cmd = "vcs -R -f test.f +v2k"
result = subprocess.run(
    cmd,
    shell=True,
    capture_output=True,   # 捕获 stdout 和 stderr
    text=True              # 以文本形式返回,而不是字节
)

print("标准输出:")
print(result.stdout)

print("标准错误:")
print(result.stderr)

print("返回码:", result.returncode)

这里 capture_output=True 是Python 3.7+ 的写法。老版本你得写 stdout=subprocess.PIPE, stderr=subprocess.PIPE。效果一样。

小技巧: 加上 text=True,输出就是字符串,不用手动 .decode()。少写一行是一行。

3.4 实时打印 vs 事后查看

仿真跑起来很慢,你肯定想实时看到日志,而不是等跑完了再看。这时候 subprocess.run 就不够用了——它要等进程结束才返回。

换成 subprocess.Popen:

import subprocess

cmd = "vcs -R -f test.f +v2k"
proc = subprocess.Popen(
    cmd,
    shell=True,
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    text=True
)

# 实时读取每一行
for line in proc.stdout:
    print(line, end='')

# 等进程结束
proc.wait()
print("返回码:", proc.returncode)

我曾经踩过一个坑:只读stdout,不读stderr。结果进程因为stderr缓冲区满了,直接卡死。你想想看,仿真跑了两个小时,最后卡在最后一行——那种感觉,啧。

避坑指南: 用Popen时,一定要同时消费stdout和stderr。要么都读,要么合并:stderr=subprocess.STDOUT。

3.5 超时处理:别让脚本死等

仿真有时候会卡住。比如某个testcase进入了死循环。脚本不能无限等下去。

import subprocess

cmd = "vcs -R -f test.f +v2k"
try:
    result = subprocess.run(
        cmd,
        shell=True,
        capture_output=True,
        text=True,
        timeout=3600  # 1小时超时
    )
except subprocess.TimeoutExpired:
    print("仿真超时!请检查testcase")

超时时间怎么设?我一般这样:

  • 单个testcase:取平均运行时间的2倍
  • 回归测试:取最慢testcase的1.5倍
  • 实在没数据:先设30分钟,跑一轮再调

3.6 完整示例:一个可复用的仿真函数

把上面的知识点串起来,写一个函数。以后每个脚本都能直接调用:

import subprocess
import sys

def run_simulation(cmd, timeout=3600):
    """
    运行仿真命令,返回 (返回码, 标准输出, 标准错误)
    """
    print(f"[INFO] 执行命令: {cmd}")
    
    try:
        result = subprocess.run(
            cmd,
            shell=True,
            capture_output=True,
            text=True,
            timeout=timeout
        )
        
        # 打印关键信息
        if result.returncode != 0:
            print(f"[WARN] 返回码非0: {result.returncode}")
            # 只打印最后20行错误,避免刷屏
            err_lines = result.stderr.strip().split('\n')
            for line in err_lines[-20:]:
                print(f"[STDERR] {line}")
        
        return result.returncode, result.stdout, result.stderr
        
    except subprocess.TimeoutExpired:
        print(f"[ERROR] 超时!命令执行超过 {timeout} 秒")
        return -1, "", "Timeout"
    except Exception as e:
        print(f"[ERROR] 未知异常: {e}")
        return -2, "", str(e)


# 使用示例
if __name__ == "__main__":
    ret, stdout, stderr = run_simulation("echo 'hello world'")
    print(f"返回码: {ret}")
这个函数我用了三年: 从单个testcase到批量回归,核心逻辑没变过。你后面写的所有脚本,都可以基于这个函数扩展。

3.7 本章知识体系

下面这张图帮你理清思路:

subprocess运行仿真命令核心流程 subprocess.run() returncode 检查 stdout / stderr timeout 处理 0=成功 非0=失败 text=True 直接读字符串 捕获 TimeoutExpired 需要实时输出?→ Popen 返回 (code, stdout, stderr)

3.8 几个容易忽略的细节

参数 作用 我踩过的坑
shell=True 通过shell执行 路径有空格时记得加引号
capture_output 捕获stdout/stderr 不设这个,输出直接打印到终端,脚本拿不到
text=True 返回字符串而非字节 忘了设的话,输出是 b'...' 格式,还得 decode
timeout 超时保护 不设的话,仿真卡死脚本也卡死

好了,第一个自动化脚本就这些内容。说白了就是三件事:跑命令、抓输出、防卡死。你把这个函数写好,后面批量跑测试用例就是套个循环的事。

我的建议: 现在就打开编辑器,把上面的 run_simulation 函数敲一遍。别复制粘贴——亲手敲一遍,印象深十倍。

可可精品资料库