python 文件对象是单次迭代器,首次遍历后即被耗尽;若在外部循环外打开命令文件,内部循环仅对第一个主机生效。解决方法是每次循环重新打开文件,或一次性读入内存为列表重复使用。
python 文件对象是单次迭代器,首次遍历后即被耗尽;若在外部循环外打开命令文件,内部循环仅对第一个主机生效。解决方法是每次循环重新打开文件,或一次性读入内存为列表重复使用。
在使用 pexpect.pxssh 批量向多台网络设备下发配置时,你可能会遇到一个典型陷阱:嵌套循环中内层文件迭代仅对首个主机生效。根本原因并非 try/except 机制、SSH 会话或 pxssh 行为异常,而是 Python 文件对象(file object)的迭代器本质——它是一次性消耗型(exhaustible iterator)。
当你执行:
c_ls = open(commands, "r")
for cmd in c_ls: # 第一次遍历:读取全部行,文件指针到达 EOF
...该文件对象 c_ls 在首次 for 循环结束后已处于“耗尽”状态(内部指针位于文件末尾),后续任何对 c_ls 的迭代(如第二次外层循环进入时)将立即结束,不产生任何元素——因此后续主机完全跳过了命令执行逻辑。
✅ 正确做法有两种,推荐后者:
立即学习“Python免费学习笔记(深入)”;
方案一:每次外层循环重新打开文件(你已验证有效)
h_ls = open(hostls, "r")
for host in h_ls:
host = host.strip()
try:
s = pxssh.pxssh(timeout=5, logfile=sys.stdout, options={
"StrictHostKeyChecking": "no",
"UserKnownHostsFile": "/dev/null",
"AddKeysToAgent": "yes"
}, encoding='utf-8')
s.login(host, user, password, auto_prompt_reset=False, original_prompt=r'.*#')
print(f">>>>> Working on {host} @ {now} <<<<<\n")
s.prompt()
# ✅ 每次连接新主机前,重新打开命令文件
with open(commands, "r") as cmd_file:
for cmd in cmd_file:
cmd = cmd.strip() # 去除换行符等空白
if not cmd: # 跳过空行
continue
s.sendline(cmd)
s.prompt()
print(s.before.decode('utf-8') if isinstance(s.before, bytes) else s.before)
s.logout()
except pxssh.ExceptionPxssh as e:
print(f"***pxssh failed on login***\n***{e} {host}***")方案二(更高效、更 Pythonic):预加载命令为列表
# 一次性读取所有命令到内存(适合命令量不大时)
with open(commands, "r") as f:
command_list = [line.strip() for line in f if line.strip()] # 过滤空行
h_ls = open(hostls, "r")
for host in h_ls:
host = host.strip()
if not host: # 跳过空主机行
continue
try:
# ... [同上建立 SSH 连接] ...
s.prompt()
# ✅ 列表可无限次迭代,安全用于嵌套循环
for cmd in command_list:
s.sendline(cmd)
s.prompt()
print(s.before.decode('utf-8') if isinstance(s.before, bytes) else s.before)
s.logout()
except pxssh.ExceptionPxssh as e:
print(f"***pxssh failed on login***\n***{e} {host}***")⚠️ 注意事项:
- 始终使用 with open(...) 管理文件资源,确保异常时自动关闭;
- f.readlines() 返回含换行符的字符串列表,建议用列表推导式 strip() 处理;
- 若命令文件极大(GB 级),避免全量加载,应采用方案一并注意文件句柄限制;
- s.before 默认为 bytes 类型(尤其启用 encoding='utf-8' 后可能为 str),打印前建议统一处理编码;
- TCL/Expect 无此问题,因其 foreach 配合 read 是重新读取文件内容,而非复用迭代器。
掌握 Python 迭代器的“一次性”特性,是写出健壮批量自动化脚本的关键基础。


















