不能直接用 shell=true 调用带用户输入的脚本,因为会交由系统 shell 解析,导致空格、$、;、| 等被注入执行;应禁用 shell=true,改用参数列表形式调用,并显式设置 shell=false、capture_output=true、timeout。

为什么不能直接用 shell=True 调用带用户输入的脚本
因为 shell=True 会把整个命令字符串交给系统 shell(如 /bin/sh)解析,一旦参数里含空格、$、;、| 或通配符,就可能被意外执行或注入。比如你拼接了用户传入的文件名:subprocess.run(f"sh ./deploy.sh {user_input}", shell=True),而 user_input 是 "app.conf; rm -rf /",后果直接失控。
安全底线是:只要参数来源不可信,就必须禁用 shell=True,改用参数列表形式。
如何正确传参给 Shell 脚本而不触发 shell 解析
Shell 脚本本身是可执行文件,subprocess 可以像调用普通二进制一样运行它——关键在于把脚本路径和参数分开传,不经过 shell 中转。
- 确保脚本有可执行权限:
chmod +x ./deploy.sh - 用列表传参,脚本路径为第一个元素:
subprocess.run(["./deploy.sh", "arg1", "arg2"]) - 如果脚本依赖
#!/bin/bash等 shebang,无需额外指定解释器;若需强制用 bash(比如用了数组语法),才显式写成:["/bin/bash", "./deploy.sh", "arg1"] - 避免用
os.path.join()拼接命令列表——它返回字符串,不是你要的分项列表
subprocess.run 必须设置的三个安全选项
默认行为在生产环境极不安全,这三个参数几乎每次都要显式声明:
-
shell=False(显式关闭,防止误继承默认值或未来版本变更) -
capture_output=True或至少stdout=subprocess.PIPE, stderr=subprocess.PIPE:避免子进程输出混入主程序日志或终端,也便于错误判断 -
timeout=30(按实际场景设值):防止脚本卡死导致主程序挂起;超时会抛subprocess.TimeoutExpired,必须捕获处理
示例:
try:
result = subprocess.run(
["./backup.sh", "/data"],
shell=False,
capture_output=True,
text=True,
timeout=60
)
if result.returncode != 0:
print("脚本失败:", result.stderr)
except subprocess.TimeoutExpired as e:
print("执行超时")
环境变量传递要克制,PATH 尤其小心
脚本常依赖 PATH 找 jq、curl 等工具,但直接传整个 os.environ 会泄露敏感变量(如 HOME、SSH_AUTH_SOCK),也可能污染脚本行为。
- 最小化传递:
env={"PATH": "/usr/local/bin:/usr/bin:/bin"} - 如需额外变量,显式构造新字典:
env=dict(os.environ, MY_VAR="value"),但记得先剔除不需要的键 - 绝对不要用
env=os.environ.copy()后删键——容易漏,且os.environ在多线程下可能被修改
真正难缠的是脚本内部又调用了其他脚本或命令,它们对环境更敏感;这种场景建议在脚本开头用 set -u 和显式 PATH=... 控制,别全指望 Python 层兜底。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











