Ubuntu 18.04 批量分子对接:Vina+MGLTools+Slurm 并行避坑 简介面向生物计算、药物设计与高性能计算集群使用者的一份实操型 PDF 资料聚焦 Ubuntu 18.04 下 Autodock Vina 批量分子对接环境的搭建与 Slurm 调度配置适合需要从单机试跑过渡到集群批处理的科研人员与高年级学生。包内共 1 个 PDF 文件约 1013KB内容以命令、脚本与配置文件片段组织便于按步骤查阅。已有 5257 人学习或下载说明该主题在分子对接入门与集群实践中关注度较高。资料覆盖 Open Babel、MGLTools、Vina 的安装与路径设置受体/配体 pdbqt 预处理vina.sh 循环对接脚本以及 munge、slurmctld/slurmd、slurm.conf 的关键配置可帮助读者理解批量任务提交、资源分配与结果格式转换的完整流程并作为环境排错和脚本改写的参考。1. 一个 96 孔的化合物库和一条敲到凌晨三点的命令第一次接触批量分子对接桌上摆着的是一份 96 个化合物的 SDF 文件和一个刚拿到结构的受体蛋白。当时我的做法很朴素打开终端把vina --receptor pro.pdbqt --ligand lig_01.pdbqt --center_x ...这条命令敲一遍改个文件名再敲一遍。敲到第 30 个的时候我开始怀疑人生——不是因为它难而是因为这活儿根本不该由人来干。后来我才知道Autodock Vina 本身是个极简的打分器它只负责给一个受体和一个配体吐一个结合能批量这件事从来都得自己搭流水线再往后数据量涨到几百上千个分子单机跑一整晚也跑不完就得把 Slurm 这类作业调度器拉进来把 for 循环拆成能在多台机器上并行跑的作业。这份资源记录的正是这条路在 Ubuntu 18.04 上装齐 Open Babel、Autodock Vina、MGLTools 三件套用一个vina.sh把串行流程跑通再装 Slurm 把它推到三个节点上并行。适合刚进生物计算方向、手上有一批分子要筛、但还没搭过流水线的开发者也适合想把实验室里那套手工脚本工程化的人。2. 工具链定位Vina 只负责打分前后处理是另一条链路2.1 三个工具各自管哪一段很多人第一次看这套流程会懵为什么装一个对接软件要装三个包因为 Autodock Vina 的输入格式是 PDBQT而 PDBQT 不是随便哪个工具都能直接产出的。它比 PDB 多了两样东西原子类型AutoDock 类型不是元素类型和部分电荷。你的受体来自 PDB 数据库配体来自化合物库的 SDF两者都得先翻译成 PDBQTVina 才认。这条翻译链路就是 MGLTools 的活儿。它里面有一堆 Python 脚本最常用的是两个prepare_receptor4.py处理受体prepare_ligand4.py处理配体。它们做的事包括加氢、补全缺失的重原子、合并非极性氢、按 AutoDock 规则重新指派原子类型、计算 Gasteiger 电荷。少了任何一步Vina 要么直接报错要么给你一个看起来正常、实际上结合能毫无意义的结果。Open Babel 则是格式搬运工。它不参与对接打分但两头都要用一头是把化合物库的 SDF 转成 MGLTools 能吃的 MOL2另一头是把 Vina 输出的 PDBQT 转回 SDF 方便下游分析。它还有一个-m参数可以把一个多分子文件拆成一堆单分子文件这是整个批量流程的关键开关。工具承担的环节关键命令输入 → 输出Open Babel格式转换、多分子拆分obabelSDF → MOL2PDBQT → SDFMGLTools受体/配体预处理prepare_receptor4.py、prepare_ligand4.pyPDB/MOL2 → PDBQTAutodock Vina构象搜索与打分vina受体 PDBQT 配体 PDBQT → 打分 PDBQT理清这张表后面所有脚本就不用死记了Vina 上下各挂一个转换器脚本无非是把这条链按分子数循环一遍。2.2 目录布局与三个 PATH 的来历原料里所有可执行文件都装在/home/autodock下面这个选择不是随手定的。Vina 是绿色包解压即用MGLTools 是install.py安装到当前目录Open Babel 走系统包管理器。三者路径各不相同如果不统一约定一个根目录脚本里就会散落一堆绝对路径换台机器就得全文替换。我沿用的布局是这样# 所有对接相关的东西都收在一个根目录下方便整体打包迁移 mkdir -p /home/autodock cd /home/autodock # Vina 是绿色二进制包解压后 bin/ 里直接就是可执行文件 tar -xzvf autodock_vina_1_1_2_linux_x86.tgz # MGLTools 需要在解压目录里跑安装脚本把包展开到 MGLToolsPckgs/ tar -xzvf mgltools_x86_64Linux2_1.5.6.tar.gz cd mgltools_x86_64Linux2_1.5.6 python2 install.py装完之后有三个路径必须进PATH缺一个脚本就得报 command not found# Vina 主程序 export PATH/home/autodock/autodock_vina_1_1_2_linux_x86/bin/:$PATH # MGLTools 自带的 python 与辅助命令行工具 export PATH/home/autodock/mgltools_x86_64Linux2_1.5.6/bin/:$PATH # 预处理脚本所在目录注意它在 MGLToolsPckgs 深处 export PATH/home/autodock/mgltools_x86_64Linux2_1.5.6/MGLToolsPckgs/AutoDockTools/Utilities24/:$PATH第三行是最容易被漏掉的。prepare_receptor4.py和prepare_ligand4.py不在bin/里而是埋在MGLToolsPckgs/AutoDockTools/Utilities24/下面很多人装完 MGLTools 发现bin/里只有pmv、adt这些 GUI 工具就以为预处理脚本没装上。其实它们在只是没进路径。把这三行写进vina.sh的开头而不是~/.bashrc好处是脚本自包含——拷到计算节点上照样能跑不依赖登录环境的配置。2.3 Ubuntu 18.04 的依赖清单逐条说清为什么装apt-get install那一长串包看着吓人其实每一条都有明确用途砍掉哪一条都会在某个环节炸出来sudo apt-get update sudo apt-get install -y \ build-essential \ # 编译某些扩展时要用 gcc/make libxt-dev libglu1-mesa-dev libxmu-dev libxi-dev \ # MGLTools 的 GUI 依赖即便跑无头模式也会链接 libpng-dev libjpeg-dev libtiff-dev \ # 分子可视化相关的图像库 csh \ # 部分老脚本 shebang 写的是 csh python-tk \ # MGLTools 里若干模块 import Tkinter python-numpy \ # 电荷计算与坐标变换依赖 python-dev # 编译 Python C 扩展时的头文件这里有一条血泪经验即使你打算全程无头运行、根本不打开 MGLTools 的图形界面libxt-dev这类 X11 开发库也不能省。原因是安装阶段的部分组件会链接这些库缺了会在install.py阶段直接失败报的还是一堆看不懂的cannot find -lXt。等你装完再回头补往往要重装一遍 MGLTools。还有一个版本陷阱MGLTools 1.5.6 全套脚本是 Python 2 写的。Ubuntu 18.04 上python默认指向 python2.7所以原文里直接python install.py能跑通但如果你的系统python已经被 alias 到 python3install.py会当场抛SyntaxError。稳妥做法是显式写python2 install.py后面所有prepare_*脚本也一样要么显式 python2 调用要么改 shebang别指望环境的默认值。3. vina.sh 拆解一个受体对 N 个配体的串行流水线3.1 受体预处理整个流程只跑一次的那一步vina.sh里第一段实质工作是受体准备原文就一行# -r 输入受体 PDB-e 遇到不支持的原子类型直接报错退出-o 输出 PDBQT prepare_receptor4.py -r pro.pdb -e -o receptor.pdbqt三个参数里-r和-o不用多说-e值得单独拎出来。它的作用是让脚本在碰到无法识别的原子类型时直接失败而不是静默地把这个原子丢掉继续往下走。听起来很严格但这恰恰是我推荐保留它的原因受体里少一个金属离子或者一个修饰残基Vina 照样能跑完、照样给你一个结合能数字只是这个数字背后的口袋形状已经错了。批处理场景下这种错误最难发现因为你不会去看每一个结果。宁可让它在第一步就崩也不要一晚上跑完 200 个分子第二天才发现受体根本不对。受体只处理一次所以这行代码放在循环外面。这是个很自然的优化但也带来一个隐患如果脚本中途被中断重跑receptor.pdbqt会被重复生成如果此时正好有作业在读它就会读到半截文件。我一般的做法是生成到临时名再mv过去原子替换避免读到不完整状态。3.2 SDF 拆包obabel 的 -m 是整个批量的开关化合物库给过来通常是一个 SDF里面按$$$$分隔了几百个分子。Vina 一次只能处理一个配体所以第一步必须拆包# SDF 先整体转成 MOL2MGLTools 对 MOL2 的兼容性最好 obabel -isd testset.sdf -omol2 -O testset.mol2 # 进到 mols 目录再拆避免拆出来的几十个文件污染工作目录 cd mols # -m 表示 split把多分子文件按分子拆成 vs1.mol2、vs2.mol2 ... obabel ../testset.mol2 -O vs.mol2 -m-m这个参数是整个流程里性价比最高的一个发现。没有它你就得自己写脚本按$$$$切分 SDF还要处理切分后每个片段的格式合法性。加上它之后mols/目录里会直接出现一批编号递增的 MOL2 文件命名规则是vs1.mol2、vs2.mol2……后面循环只要ls *.mol2就能遍历。这里有个细节为什么要先转 MOL2 再拆而不是直接从 SDF 拆因为 MGLTools 的prepare_ligand4.py虽然名义上支持 SDF但对 SDF 里的键级和芳香性处理不如 MOL2 稳。MOL2 显式记录了键类型和原子类型转换损耗小。多一次转换少一堆莫名其妙的配体原子数不对报错这笔账划得来。3.3 循环体三步走配体准备、对接、结果回写核心循环只有十几行但每一步都扣着下一步cd mols for mol2 in ls *.mol2; do # 第一步MOL2 转成 Vina 能读的 PDBQT加氢、算电荷都在这一步完成 prepare_ligand4.py -l $mol2 -o ligand.pdbqt # 第二步对接。--cpu 1 是关键批处理时不要让它吃满所有核 vina --config ../conf.txt --cpu 1 --out out.pdbqt --log log.txt # 第三步PDBQT 转回 SDF追加到总结果文件 obabel -ipdbqt out.pdbqt -osd -O temp.sdf cat temp.sdf output.sdf # 清理中间文件否则下一轮循环会被上一轮的残留污染 rm -f log.txt ligand.pdbqt out.pdbqt temp.sdf done cd ../第一步的prepare_ligand4.py只接受单分子输入输出固定叫ligand.pdbqt所以每轮循环都会覆盖它。这没问题因为第二步紧接着就消费掉了。但如果第一步失败比如某个 MOL2 的键信息有误第二步会拿上一轮残留的ligand.pdbqt去对接结果就是——某个分子的对接结果其实是另一个分子的。这种错误在最终的output.sdf里完全看不出来除非你回头比对分子数。这就是末尾rm -f那行存在的意义。它不是可有可无的清理而是防串味的保险。我见过有人为了保留现场方便调试把这行注释掉结果跑出来的结果里混着重复分子排查了大半天。要调试可以在rm前加一句if [ $? -ne 0 ]; then mv ligand.pdbqt fail_$mol2.pdbqt; fi把失败样本单独留档但绝不能让它留在原位置等下一轮覆盖。--cpu 1也值得说一下。Vina 默认会吃满机器上所有核单机跑单个分子时这是好事但在批量循环里每个分子都开满核意味着这些分子实际上是串行争抢 CPU 的总耗时不会变短反而因为线程调度开销略微变长。上了集群之后这一条更是硬性要求——一个作业申请 1 个核就只能用 1 个核超用会被 cgroup 掐掉。所以从一开始就把它固定成 1后面迁移到 Slurm 时脚本几乎不用改。3.4 conf.txt真正影响结果和耗时的六个参数vina命令本身除了--config、--out、--log之外几乎不带参数所有对接配置都在conf.txt里。这个文件没有模板得自己写格式是键 值# 受体与配体路径相对 vina 命令的执行目录脚本里是 mols/所以用 .. receptor ../receptor.pdbqt ligand ligand.pdbqt # 搜索盒子中心一般取受体的结合口袋或已知配体的几何中心 center_x 12.5 center_y -3.2 center_z 18.7 # 搜索盒子边长单位埃。宁大勿小但太大也会稀释搜索效率 size_x 22 size_y 22 size_z 22 # 搜索强度默认 8。批量筛选常用 8~16再高耗时线性涨 exhaustiveness 8 # 每个配体输出几个构象一般 9 够用配合 energy_range 做后续筛选 num_modes 9 energy_range 3 # 固定随机种子保证同一输入重跑结果一致做复现时必加 seed 42参数作用批处理场景下的取值建议center_x/y/z定义搜索盒中心用已知配体或口袋残基的质心不要目测size_x/y/z搜索盒边长埃20~25 之间覆盖口袋即可exhaustiveness搜索强度初筛 8复筛 16~32num_modes输出构象数9默认配合energy_range过滤energy_range输出构象的能量窗口kcal/mol3超过这个差值的构象没意义seed随机种子固定值保证可复现receptor和ligand这两行用相对路径是整个脚本里最容易翻车的地方。vina命令是在mols/目录下执行的所以receptor要写../receptor.pdbqtligand写ligand.pdbqt就在当前目录。一旦你把脚本改成从别的目录启动这两个路径立刻失效报错信息还很不直观——Parse error on line 1 in file conf.txt看起来像是配置文件格式问题实际上是文件找不到。exhaustiveness是唯一一个直接决定跑多久的参数。它控制蒙特卡洛搜索的采样次数默认 8 时一个中等大小的配体在单核上大概几十秒调到 32 就是几分钟。批量筛选第一轮没必要用高值先用 8 把所有分子过一遍拿到结合能排序只对排名靠前的几十个做高精度复算这是我最常用的两段式策略。4. Slurm 落地把 for 循环拆成三个节点上的作业4.1 mungeSlurm 的认证底座先把它跑通Slurm 的组件之间要互相认证认证靠的不是密码而是 munge。munge 在每台机器上有一个共享密钥所有节点密钥一致才能互信。所以安装顺序必须是先 munge 后 Slurm反过来装完 Slurm 起不来报的错还都是认证失败很容易误判成配置文件问题。# munge 本体 开发库Slurm 编译/链接 auth/munge 插件时需要 apt-get install -y libmunge-dev libmunge2 munge # 开机自启并立刻拉起 systemctl enable munge systemctl start munge # 验证能打印出解码后的凭证就说明服务正常 munge -n | unmungemunge -n | unmunge是必做的自检。如果这条命令报Invalid credential或者连不上 socket后面 Slurm 百分百起不来不用往下折腾了。多节点场景下/etc/munge/munge.key必须三台机器完全一致权限必须是0400且属主是munge用户——权限宽了 munge 会拒绝启动这是个很经典的坑。4.2 slurm.conf从模板里只挑十来行改官方配置生成器会吐出一份几百行的slurm.conf绝大部分是注释掉的可选项。实际必须手改的只有十来行剩下的保持默认即可。下面是我在三节点环境里用的精简版# 集群标识随便起但要和后续命令里的 cluster 名对上 ClusterNamecluster # 控制节点的主机名不是 IP必须能被所有节点解析 SlurmctldHostnode01 # 守护进程以谁的身份跑。全 root 在小集群里最省事 SlurmUserroot SlurmdUserroot # 状态与 spool 目录必须提前创建且路径在所有节点一致 StateSaveLocation/var/spool/slurmctld SlurmdSpoolDir/var/spool/slurmd # 进程追踪方式pgid 在容器/云主机上兼容性最好 ProctrackTypeproctrack/pgid SelectTypeselect/linear # 三个计算节点CPUs 要和实际核数一致写错会导致作业永远 PENDING NodeNamenode01 NodeAddr10.0.0.11 CPUs1 StateUNKNOWN NodeNamenode02 NodeAddr10.0.0.12 CPUs1 StateUNKNOWN NodeNamenode03 NodeAddr10.0.0.13 CPUs1 StateUNKNOWN # 分区把节点挂到分区里作业才能提交 PartitionNamep1 Nodesnode01 DefaultYES MaxTimeINFINITE StateUP PartitionNamep2 Nodesnode02 DefaultYES MaxTimeINFINITE StateUP PartitionNamep3 Nodesnode03 DefaultYES MaxTimeINFINITE StateUPSlurmctldHost这一行原文用的是主机名而 node 定义里用的又是另一套命名两边对不上的时候slurmd会报Unable to register this node但服务状态看起来是 active 的很有迷惑性。原则很简单SlurmctldHost、NodeName三处名字必须和机器上hostname的输出完全一致一个字符都不能差包括大小写。另一个高频翻车点是StateSaveLocation和SlurmdSpoolDir指向的目录不存在。Slurm 不会自动创建这两层目录启动时直接失败日志里只有一行Cannot write to state save location。装完第一件事就是手动建目录# 两个目录权限要够SlurmUser 是谁就给谁 mkdir -p /var/spool/slurmctld /var/spool/slurmd建完再systemctl start slurmctld和systemctl start slurmd然后用sinfo看节点状态。正常的输出里三个节点应该都是idle如果显示down或drain用scontrol show node node01看Reason字段比翻日志快得多。4.3 把 for 循环塞进 sbatch 脚本单机版vina.sh改造成作业脚本改动其实只有头部几行 SBATCH 指令和一个目录切换#!/bin/bash #SBATCH -J vina_p1 # 作业名squeue 里显示的就是它 #SBATCH -p p1 # 提交到哪个分区对应 slurm.conf 里的 PartitionName #SBATCH -N 1 # 申请节点数 #SBATCH -n 1 # 每节点任务数这里一个作业就一个进程 #SBATCH -o job.%j.out # 标准输出%j 会被替换成作业 ID #SBATCH -t 24:00:00 # 墙钟时间上限超时会被强杀必须设 # 环境变量必须在这里重新 exportslurm 不会继承你的登录 shell export PATH/home/autodock/autodock_vina_1_1_2_linux_x86/bin/:$PATH export PATH/home/autodock/mgltools_x86_64Linux2_1.5.6/bin/:$PATH export PATH/home/autodock/mgltools_x86_64Linux2_1.5.6/MGLToolsPckgs/AutoDockTools/Utilities24/:$PATH # 关键cd 到共享存储上的工作目录不能用相对路径瞎跑 cd /vina/mols for mol2 in ls *.mol2; do prepare_ligand4.py -l $mol2 -o ligand.pdbqt || continue vina --config ../conf.txt --cpu 1 --out out.pdbqt --log log.txt obabel -ipdbqt out.pdbqt -osd -O temp.sdf cat temp.sdf output.$SLURM_JOB_ID.sdf rm -f log.txt ligand.pdbqt out.pdbqt temp.sdf done两个变化值得展开。第一个是export PATH必须在作业脚本里重写一遍。很多人以为~/.bashrc里配好了就行结果sbatch提交上去秒退.out里就一行prepare_ligand4.py: command not found。原因是 Slurm 执行作业用的是非交互式 shell不会加载.bashrc。解决办法要么在脚本里显式 export推荐脚本自包含要么在sbatch时加--exportALL但后者依赖提交节点的环境换个人提交就失效。第二个是输出文件名带了$SLURM_JOB_ID。因为三个节点会同时跑如果都往同一个output.sdf追加NFS 上的并发写会让内容交错最后拼出来的 SDF 里分子结构互相穿插连obabel都解析不了。让每个作业写自己的文件跑完再合并是唯一稳妥的做法。4.4 三节点与共享目录让任务在哪台节点跑都一样多节点并行的前提是所有节点看到同一份数据。做法是把工作目录挂到 NFS 上三台机器都挂到同一个挂载点比如/vina这样作业脚本里的/vina/mols在哪个节点上解析结果都一样。挂载之后先验证一遍# 在 node01 上写文件到 node02 上读能读到才算挂载成功 echo test /vina/mols/_probe ssh node02 cat /vina/mols/_probe共享目录解决之后并行化的方式有两种。一种是简单粗暴地按分子切分node01 跑前 1/3node02 跑中间 1/3node03 跑最后 1/3人工分成三个目录各提交一个作业。这种做法直观但负载不均——如果某些分子特别大、对接特别慢某个节点就会拖后腿。另一种是作业数组让 Slurm 自己分配#SBATCH --array1-96 # 展开成 96 个独立作业每个拿到不同的 SLURM_ARRAY_TASK_ID #SBATCH -p p1,p2,p3 # 允许调度到三个分区哪个闲就往哪放作业数组配合一个任务处理一个分子的脚本负载均衡是调度器的事你只需要保证编号和文件能对上。缺点是数组里的每个任务都是独立进程如果你把整个 for 循环塞进一个数组元素里数组就白用了。正确姿势是让SLURM_ARRAY_TASK_ID直接索引到一个配体文件# 从编号算出对应的配体文件名一个任务只处理一个分子 lig$(ls vs*.mol2 | sed -n ${SLURM_ARRAY_TASK_ID}p) prepare_ligand4.py -l $lig -o ligand.pdbqt vina --config ../conf.txt --cpu 1 --out out_${SLURM_ARRAY_TASK_ID}.pdbqt --log log_${SLURM_ARRAY_TASK_ID}.txt这样 96 个分子就是 96 个作业三个节点自动分摊跑完sacct -j jobid能看到每个任务的耗时分布哪个分子拖后腿一目了然。5. 避坑与排查几个让我重跑整晚的问题5.1 现象作业跑完退出码 0但 output.sdf 是空的原因通常是vina那一步失败了但脚本没检查返回码。vina因为配体 PDBQT 不完整或者盒子坐标落在蛋白外部而退出时obabel会对着一个不存在的out.pdbqt报错但cat temp.sdf output.sdf这句依然会执行temp.sdf可能是上一轮残留结果就是循环继续、作业正常结束。解决办法是在每一步后面加|| exit 1或者set -e让失败立刻终止并且把rm -f挪到循环开头而不是结尾保证每轮都是从干净状态开始。5.2 现象sbatch 提交后一直 PENDINGReason 写 Resources先跑scontrol show node看节点是不是drain或down。常见原因是slurm.conf里写的CPUs和机器实际核数不一致或者NodeAddr填的 IP 和节点真实 IP 对不上导致slurmd注册失败。还有一个隐蔽的原因分区里DefaultYES写了多个Slurm 会认为配置冲突作业找不到可用分区。一个分区只能有一个默认改完记得三台机器同步配置文件并systemctl restart slurmctld。5.3 现象prepare_ligand4.py 报 SyntaxError 或 import 失败基本可以断定是 Python 版本问题。MGLTools 1.5.6 的脚本是 Python 2 语法print语句、except X, e这种写法在 python3 下必然崩。用head -1 $(which prepare_ligand4.py)看一眼 shebang如果是#!/usr/bin/env python而你系统里python指向 3.x就显式用python2调用或者把 shebang 改成python2。另一类报错是ImportError: No module named Tkinter装python-tk解决注意要装 python2 的包python3-tk不管用。5.4 现象多节点同时跑合并后的 SDF 用 obabel 读会报错这是并发写同一个文件的典型症状。三个作业都往output.sdf里catNFS 的写操作不保证原子性两段 SDF 记录会交错插进彼此中间。排查方法很简单grep -c $$$$ output.sdf如果这个数不等于配体总数就是写冲突。解决就是上一章说的每个作业写带$SLURM_JOB_ID或$SLURM_ARRAY_TASK_ID的独立文件全部跑完再用一条串行命令合并合并时顺手grep -c校验一次分子数。5.5 现象slurmd 起来了但节点状态是 down日志写 Unable to register this node三个方向排查主机名不一致hostname输出和slurm.conf里的NodeName有大小写或后缀差异比如一个带域名一个不带、munge.key不同步三台机器的 key 文件必须字节一致直接scp过去最省事注意权限保持0400、时间偏差过大munge 对时间敏感节点间差几分钟就会认证失败装个chrony对齐时间。排查顺序建议是主机名 → munge → 时间从最常见的往下走。6. 二次开发把一次性脚本改成可续跑的任务清单这套流程跑顺之后最难受的场景变成了跑到第 180 个分子机器重启了。vina.sh是个纯 for 循环没有任何进度记录重跑只能从头开始而前面 179 个分子的算力就白烧了。我后来做的最小改动是引入一份任务清单预处理阶段先扫一遍配体目录把每个分子和它的完成状态写进一张表跑的时候只挑没完成的。import os, glob, re # 扫描输出目录从已经合并的 SDF 里反推哪些配体已经算完 done set() for sdf in glob.glob(/vina/results/*.sdf): with open(sdf) as f: for line in f: # MGLTools 转换后 SDF 首行是分子名和 vsN.mol2 的 N 对得上 m re.match(rvs(\d), line.strip()) if m: done.add(int(m.group(1))) break # 每个文件只取首行避免大文件全读 # 生成待跑清单编号 分子路径一行一个任务 with open(/vina/todo.txt, w) as out: for mol2 in sorted(glob.glob(/vina/mols/vs*.mol2)): idx int(re.search(rvs(\d), mol2).group(1)) if idx not in done: out.write(f{idx}\t{mol2}\n) print(f已完成 {len(done)} 个待跑 {sum(1 for _ in open(/vina/todo.txt))} 个)这段脚本的价值在于它把状态从脚本的执行流里抽了出来变成磁盘上可检查的数据。跑之前看一眼todo.txt的行数就知道还剩多少中断之后重跑它会自动跳过已完成的。配合--array使用数组长度直接用wc -l todo.txt拿到任务 ID 再映射到清单行号# 数组范围由清单行数决定避免固定写 1-96 导致空跑 n$(wc -l /vina/todo.txt) sbatch --array1-${n} vina_array.sh # 作业脚本里按行号取任务而不是 ls 后 sed 取第 N 行 line$(sed -n ${SLURM_ARRAY_TASK_ID}p /vina/todo.txt) idx$(echo $line | cut -f1) mol2$(echo $line | cut -f2)改动量不大但收益是实打实的任务可以分多次提交中途加新分子只要重跑一遍生成清单的脚本Slurm 那边不用动。再往后如果要做得更正规可以把清单换成一张 SQLite 表字段加上开始时间、耗时、结合能后面做结果排序和失败重试都从表里查脚本本身彻底无状态。我现在的习惯是任何超过 50 个样本的批处理先花十分钟写清单生成和状态检查再动手写执行脚本。以前总觉得这是在浪费时间直到有一次一个跑了十四个小时的作业因为 NFS 抖动中断而我没有断点记录只能整晚重跑——从那以后我每次搭这类流水线都会先把跑到一半挂了怎么办这个问题回答掉再开始写第一行循环。希望这些踩过的坑能帮你少走一段。本文还有配套的精品资源点击获取