如何使用Python subprocess模块查询Windows外部应用KeePass的运行状态
我通过Python的subprocess模块调用Windows程序KeePass时,无法判断程序的启动状态。调用KeePass本身操作简单,但调用完成后无法确定它是否成功启动。
我查阅了Popen.poll()、Popen.wait()和Popen.communicate()的相关文档并完成测试,但未找到可用于判断状态的依据。
示例代码如下:(参考代码中# <--- What goes here to check status?注释,此处应填入什么判断逻辑?)
import subprocess as s import os.path keepass_app = 'C:\Program Files\KeePass Password Safe 2\KeePass.exe' keepass_db = 'E:\Documents\test.kdbx' pw_file = 'E:\\Documents\\test.md' def check_each_pw(pw_file): for pw in pw_file: t = s.Popen([keepass_app, keepass_db, f'-pw:{pw}'], stdin=s.PIPE, stdout=s.PIPE, stderr=s.PIPE) if : # <--- What goes here to check status? print(f'{pw} is not a valid password') t.terminate() else: print(f'{pw} is the correct password!') t.terminate() check_each_pw(pw_file)
补充测试1:
我在上述代码中添加output, errors = t.communicate()逻辑,测试发现无论密码是否正确,返回的output和error值均为b'' b'',无区分度:
t = s.Popen([keepass_app, keepass_db, f'-pw:{pw}'], stdin=s.PIPE, stdout=s.PIPE, stderr=s.PIPE) output, errors = t.communicate() if errors == b'': print(f'{pw} is the correct password!') print(output, errors) t.terminate() else: print(f'{pw} is not a valid password.!') print(output, errors) t.terminate()
测试结果:pw_file第一行为错误密码,此时KeePass数据库无法打开,GUI弹出报错窗口,需要手动关闭;第二行为正确密码,KeePass成功启动。但两次运行的t.communicate()终端输出完全一致,无可用作判断的差异值:
PS E:\Documents\python\pw_program> python .\pw_prog.py abc is the correct password! b'' b'' test is the correct password! b'' b''
使用t.wait()测试也存在相同问题,两次返回值均为0,无区分度:
t = s.Popen([keepass_app, keepass_db, f'-pw:{pw}'], stdin=s.PIPE, stdout=s.PIPE, stderr=s.PIPE) errors = t.wait() if errors == 0: print(f'{pw} is the correct password!') print(errors) t.terminate() else: print(f'{pw} is not a valid password.') print(errors) t.terminate()
PS E:\Documents\python\pw_program> python .\pw_prog.py abc is the correct password! 0 test is the correct password! 0
使用t.poll()测试结果也一致,两次返回值均为None,无法完成判断:
t = s.Popen([keepass_app, keepass_db, f'-pw:{pw}'], stdin=s.PIPE, stdout=s.PIPE, stderr=s.PIPE) errors = t.poll() if errors is None: print(f'{pw} is the correct password!') print(errors) t.terminate() else: print(f'{pw} is not a valid password.') print(errors) t.terminate()
PS E:\Documents\python\pw_program> python .\pw_prog.py abc is the correct password! None test is the correct password! None
我还尝试替换使用s.check_call和s.check_output测试,也未能解决问题。
解决方案
问题根因
KeePass 是 GUI 类程序,默认不会将鉴权错误信息输出到标准输出/标准错误流,且无论密码是否正确,进程启动后都会持续驻留(密码错误时会弹出模态错误窗口,进程不会主动退出),因此你通过subprocess内置的进程状态查询方法无法获取到有效的区分信息。
可选实现方案
方案1:使用专用库直接读取KeePass数据库(最推荐)
不需要调用KeePass主程序,直接通过pykeepass库完成密码校验,实现简单、执行效率更高:
- 先安装依赖包:
pip install pykeepass
- 代码示例:
from pykeepass import PyKeePass from pykeepass.exceptions import CredentialsError keepass_db = 'E:\Documents\test.kdbx' pw_file = 'E:\\Documents\\test.md' def check_each_pw(pw_file): # 读取密码列表 with open(pw_file, 'r', encoding='utf-8') as f: pw_list = [line.strip() for line in f if line.strip()] for pw in pw_list: try: # 尝试用当前密码打开数据库 PyKeePass(keepass_db, password=pw) print(f'{pw} is the correct password!') # 找到正确密码后可以直接终止循环 break except CredentialsError: print(f'{pw} is not a valid password') check_each_pw(pw_file)
方案2:搭配KeePass命令行参数实现判断(无需额外依赖)
KeePass 命令行支持-exit参数,程序启动后如果成功打开数据库会自动执行退出逻辑;密码错误时则会停留在错误弹窗,进程不会退出,搭配超时逻辑即可判断:
import subprocess as s import time keepass_app = 'C:\Program Files\KeePass Password Safe 2\KeePass.exe' keepass_db = 'E:\Documents\test.kdbx' pw_file = 'E:\\Documents\\test.md' def check_each_pw(pw_file): with open(pw_file, 'r', encoding='utf-8') as f: pw_list = [line.strip() for line in f if line.strip()] for pw in pw_list: t = s.Popen([keepass_app, keepass_db, f'-pw:{pw}', '-exit'], stdin=s.PIPE, stdout=s.PIPE, stderr=s.PIPE) # 等待3秒,足够正常完成数据库打开和自动退出流程 time.sleep(3) ret_code = t.poll() # 进程已退出说明密码正确,成功打开后执行了退出逻辑 if ret_code is not None: print(f'{pw} is the correct password!') break else: print(f'{pw} is not a valid password') t.terminate() check_each_pw(pw_file)
方案3:Windows窗口检测判断(适合需要保留KeePass运行的场景)
如果你的使用场景需要在密码正确后保留KeePass运行,可以通过pywin32库枚举系统窗口,匹配窗口标题判断状态:错误弹窗的标题为KeePass,包含密码错误相关提示;成功打开后窗口标题会携带数据库名称。
- 安装依赖:
pip install pywin32
内容的提问来源于stack exchange,提问作者SeaDude

