为什么 subprocess 调用 pg_dump 时会连接到默认数据库?
结论
两种调用的行为不同:os.system 会把整段字符串交给 shell 解析,而 subprocess.Popen 在 shell=True 时接收的是参数列表。列表里的 > 和文件路径不会像 shell 命令字符串中的重定向符那样生效。因此,pg_dump 没有收到预期的数据库名 sdtests,输出也没有按预期重定向。
在 POSIX shell 下,Popen 使用 shell=True 并接收序列时,序列首项会作为 shell 命令字符串。原调用的首项是 pg_dump,因此 shell 实际执行的命令相当于 pg_dump;sdtests、> 和文件路径都不会作为该命令的参数或重定向来解析。未指定数据库名时,pg_dump 会尝试使用当前操作系统用户名作为数据库名。错误中的 bret 与这种情况相符。
推荐写法:直接把标准输出写入文件
将 shell 保持为默认值 False,把数据库名作为参数传给 pg_dump,再用 Python 打开目标文件:
import subprocess
command = ["pg_dump", "sdtests"]
with open("/home/bret/dump.custom", "w") as dump_file:
result = subprocess.run(
command,
stdout=dump_file,
stderr=subprocess.PIPE,
text=True,
)
if result.returncode != 0:
raise RuntimeError(f"pg_dump failed: {result.stderr}")
> 和输出路径不需要放进 command。stdout=dump_file 会将命令的标准输出写入文件,stderr 则保留错误信息供排查。
如果确实需要 shell 重定向
也可以传入一整条 shell 命令字符串:
import subprocess
command = "pg_dump sdtests > /home/bret/dump.custom"
result = subprocess.run(command, shell=True, text=True, capture_output=True)
if result.returncode != 0:
raise RuntimeError(f"pg_dump failed: {result.stderr}")
这种写法会由 shell 解析 >。如果命令包含变量或外部输入,拼接字符串可能带来 shell 注入风险。能用参数列表和 stdout 文件对象时,优先采用前一种方式。
注意事项
以上解释适用于题目中的 POSIX shell 环境。如果数据库名、连接参数或环境与示例不同,实际连接目标也可能受 pg_dump 参数、环境变量和 PostgreSQL 连接配置影响。示例中的 pg_dump sdtests 会将 sdtests 作为数据库名传入。若仍报连接错误,请确认数据库存在,并检查当前用户是否有相应的连接权限。
备注:内容仅供参考。
