自动执行多个终端命令-Python

深度学习实验,后期有许多要交叉验证或者需要多对比的实验,一个一个在终端修改命令参数再执行太麻烦了,可以用subprocess的方法,在某文件中录入所有需要执行的命令,程序自动书序读取执行。


一、需求分析

在科研或开发过程中,我们经常需要批量执行多个实验命令,比如训练多个模型、不同参数组合的对比实验等。而手动逐个运行不仅麻烦,还容易出错,尤其当某些命令执行失败需要重试的时候。

为了解决这一痛点,该脚本的目标是:

  • 从文件中批量读取命令
  • 逐个执行,并记录日志
  • 若命令执行失败,支持自动重试
  • 实时输出运行状态

简而言之,这是一个批处理实验命令的执行器,适合用于科研实验或部署任务中。


二、整体结构梳理

代码核心结构分为三部分:

函数名 功能说明
log(message) 统一日志记录函数,打印并写入日志文件
run_command(command, index) 核心命令执行逻辑,支持实时输出和自动重试
main() 主流程:读取命令文件,循环执行每条命令

全局变量设置了日志文件路径、命令文件路径、最大重试次数。

这里,假设需要运行的脚本的名称是train.py,参数通过命令传入。你需要对模型、batch size、数据集路径和其他超参数进行组合测试:

假设需要以下参数支持:

1
2
3
4
5
6
7
8
9
--model:模型结构名称,如 UNet, TransUNet, SwinUNet

--batch_size:批大小

--lr:学习率

--trainset、--validset、--testset:数据集路径

--epochs:训练轮数(可选)

那我们可以把顺序读取命令的文件experiments_auto.txt内按照如下方式记录:每一行一个命令。

1
2
3
4
5
python train.py --model UNet --batch_size 8 --lr 0.001 --trainset ./data/fold1/train --validset ./data/fold1/valid --testset ./data/fold1/test --epochs 100
python train.py --model UNet --batch_size 16 --lr 0.0005 --trainset ./data/fold2/train --validset ./data/fold2/valid --testset ./data/fold2/test --epochs 100
python train.py --model TransUNet --batch_size 8 --lr 0.001 --trainset ./data/fold1/train --validset ./data/fold1/valid --testset ./data/fold1/test --epochs 100
python train.py --model TransUNet --batch_size 16 --lr 0.0005 --trainset ./data/fold2/train --validset ./data/fold2/valid --testset ./data/fold2/test --epochs 100
python train.py --model SwinUNet --batch_size 8 --lr 0.0001 --trainset ./data/fold3/train --validset ./data/fold3/valid --testset ./data/fold3/test --epochs 100

三、代码细节解析

1. CMD_FILELOG_FILE

1
2
3
CMD_FILE = 'experiments_auto.txt'
LOG_FILE = 'experiment_log.txt'
MAX_RETRIES = 3
  • experiments_auto.txt:你要提前准备的命令清单文件,每一行一个命令。
  • experiment_log.txt:用于记录命令运行时的所有输出和状态
  • MAX_RETRIES = 3:每个命令失败后会最多尝试 3 次

2. 日志函数:log(message)

1
2
3
4
5
def log(message):
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
full_message = f"[{timestamp}] {message}"
print(full_message)
...
  • 打印带时间戳的信息
  • 同时写入到 LOG_FILE
  • 保证运行过程中出现的每一条信息都能被追踪和复现

3. 执行器:run_command(command, index)

这是脚本的核心逻辑部分,关键点如下:

1
process = subprocess.Popen(...)
  • 使用 subprocess.Popen 启动命令,支持实时读取输出流
  • 使用 stdout=subprocess.PIPE + for line in process.stdout 实现输出实时打印效果
  • 成功执行后返回 True,失败最多重试 MAX_RETRIES
1
2
if process.returncode == 0:
log(f"成功执行第 {index+1} 条命令。\n")
  • process.returncode 用来判断命令是否成功退出(0 表示成功)
  • 失败则进入 except subprocess.CalledProcessError 分支,写入失败日志并重试

4. 主流程:main()

1
2
if os.path.exists(LOG_FILE):
os.remove(LOG_FILE) # 清空旧日志
  • 运行前清空日志文件,确保这次实验结果是干净的
1
2
with open(CMD_FILE, 'r', encoding='utf-8') as f:
commands = [line.strip() for line in f if line.strip()]
  • 从文件中读取命令,并清洗空行
  • 遍历所有命令,调用 run_command

四、使用说明

1. 准备命令文件

创建一个名为 experiments_auto.txt 的文件,每一行写一条你希望执行的命令,例如:

1
2
3
python train.py --config config1.yaml
python train.py --config config2.yaml
python evaluate.py --model model1.pth

2. 运行脚本

直接运行:

1
python run_multi_experiments.py

然后你会看到控制台和 experiment_log.txt 文件中有实时输出。

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
"""
@Project Name:
@File Name: run_multi_experiments
@Author:
@Created: 2025/4/18 14:17
@Version: 1.1
@Description: 带日志记录与实时输出的多命令运行器,支持自动重试
"""

import subprocess
import time
import os

CMD_FILE = 'experiments_auto.txt'
LOG_FILE = 'experiment_log.txt'
MAX_RETRIES = 3

def log(message):
"""写入日志并打印到控制台"""
timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
full_message = f"[{timestamp}] {message}"
print(full_message)
with open(LOG_FILE, 'a', encoding='utf-8') as log_f:
log_f.write(full_message + '\n')

def run_command(command, index):
"""尝试执行命令,支持自动重试和实时输出"""
for attempt in range(1, MAX_RETRIES + 1):
log(f"第 {index+1} 条命令:尝试第 {attempt} 次运行:{command}")
try:
process = subprocess.Popen(
command,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
universal_newlines=True,
bufsize=1
)

with open(LOG_FILE, 'a', encoding='utf-8') as log_f:
for line in process.stdout:
print(line, end='') # 实时打印
log_f.write(line) # 同时记录日志

process.wait()
if process.returncode == 0:
log(f"成功执行第 {index+1} 条命令。\n")
return True
else:
raise subprocess.CalledProcessError(process.returncode, command)

except subprocess.CalledProcessError:
log(f"命令执行失败(第 {attempt} 次)")
if attempt < MAX_RETRIES:
time.sleep(5)
else:
log(f"第 {index+1} 条命令在 {MAX_RETRIES} 次尝试后仍失败,跳过该命令。\n")
return False

def main():
if os.path.exists(LOG_FILE):
os.remove(LOG_FILE) # 每次运行前清空旧日志

with open(CMD_FILE, 'r', encoding='utf-8') as f:
commands = [line.strip() for line in f if line.strip()]

log(" 实验开始运行...\n")

for idx, cmd in enumerate(commands):
run_command(cmd, idx)

log("所有命令执行完毕。\n")

if __name__ == "__main__":
main()


五、总结

这个脚本简单实用,是科研或开发中批量执行命令的好帮手。它结合了日志记录、实时输出、失败重试等功能,可靠性强、可读性高,非常适合做成通用工具或集成进自己的项目中。

现在其实还有一些深度学习管理平台可以可视化编辑每次运行的超参数,等以后学到了之后补充。

作者

Zhou

发布于

2025-04-20

更新于

2025-04-21

许可协议

评论

+ + +