Python:subprocess 前先用 shutil.which 找命令

46 次浏览3 条回复

写小脚本调外部工具时,直接 subprocess.run(["ffmpeg", ...]),机器上没装的话会在运行到那一步才炸 FileNotFoundError。如果想把错误提示做得友好一点,可以先用标准库的 shutil.which() 探一下。

环境:Python 3.10+,Linux/macOS/Windows,只用标准库。保存为 which-before-run.py

import shutil
import subprocess
import sys


def require_command(name: str) -> str:
    path = shutil.which(name)
    if path is None:
        raise SystemExit(f"missing command: {name}")
    return path

python = require_command("python3" if sys.platform != "win32" else "python")

result = subprocess.run(
    [python, "--version"],
    text=True,
    capture_output=True,
    check=True,
)

print(result.stdout.strip() or result.stderr.strip())

跑一下:

python3 which-before-run.py

这个适合放在脚本启动阶段,缺什么直接报清楚。还有个小细节:which() 返回的是解析后的路径,后面 subprocess.run() 继续用列表参数就行,别为了拼命令字符串再绕回 shell。

这个写法挺实用,尤其是脚本一开始要检查一串外部依赖的时候。

我一般还会顺手把多个命令攒起来一起报,避免用户修一个再跑一次又发现下一个缺了,比如:

import shutil

missing = [name for name in ["git", "python3", "ffmpeg"] if shutil.which(name) is None]
if missing:
    raise SystemExit("missing commands: " + ", ".join(missing))

后面真正执行时再用 shutil.which() 返回的路径,或者继续用命令名让 PATH 解析,都比拼 shell 字符串稳。

qqqLv1#1

这个写法挺实用,尤其是脚本一开始要检查一串外部依赖的时候。

我一般还会顺手把多个命令攒起来一起报,避免用户修一个再跑一次又发现下一个缺了,比如:

import shutil

missing = [name for name in ["git", "python3", "ffmpeg"] if shutil.which(name) is None]
if missing:
    raise SystemExit("missing commands: " + ", ".join(missing))

后面真正执行时再用 shutil.which() 返回的路径,或者继续用命令名让 PATH 解析,都比拼 shell 字符串稳。

补一嘴:如果是给 venv 里的工具做预检查,我会把 shutil.which() 解析出来的路径顺手打到日志里,排查起来快很多。比如同名命令在系统 Python 和虚拟环境里可能不是一个入口,print(shutil.which("pytest")) 一眼就能看出来走的是哪份。

324Lv1#2

补一嘴:如果是给 venv 里的工具做预检查,我会把 shutil.which() 解析出来的路径顺手打到日志里,排查起来快很多。比如同名命令在系统 Python 和虚拟环境里可能不是一个入口,print(shutil.which("pytest")) 一眼就能看出来走的是哪份。

这个补充挺实用。

我再加一个小坑:如果命令是给别的 Python 环境准备的,shutil.which() 找到的路径最好也顺手打印出来,像 sys.executablepippytest 这种同名入口,最容易因为 PATH 顺序不一样看懵。

我一般会把“是否存在”和“实际走到哪一个路径”分开看,排错会快一点。