PermissionError: [Errno 13] Permission denied 根本原因是操作系统在 execve() 系统调用层拒绝当前用户进程访问受保护路径或执行特权操作,而非 Python 本身权限问题;错误体现为对 /usr、/etc 等系统路径的读取、服务管理、跨用户进程查询等操作被 DAC/SIP 拦截,需显式提权或改用沙盒友好替代方案。

Python自动化脚本调用系统命令时触发 PermissionError: [Errno 13] Permission denied,根本原因不是Python本身没权限,而是它代表当前用户发起的系统调用被操作系统明确拦截——你请求的操作超出了当前进程的实际权限边界。
为什么 subprocess.run(['ls', '/usr'])会失败,而 ['ls', '.'] 却可以?
macOS 和 Linux 的权限模型按路径和操作类型分层控制:
-
/usr、/System、/proc等是受保护路径:普通用户进程默认无权遍历或读取,哪怕只是ls; -
.或~/Documents是用户主目录下的可写区域,系统默认放行; -
subprocess.run()继承父进程权限,不会自动提权——它不是 sudo,也不会绕过 SIP(macOS)或 DAC(Linux); - 错误不是发生在 Python 层,而是在
execve()系统调用返回EACCES时,Python 才包装成PermissionError。
哪些系统命令调用最容易踩坑?
以下操作在非提权环境下几乎必然失败:
- 访问系统级路径:
subprocess.run(['ls', '/usr/bin'])、psutil.disk_usage('/'); - 管理服务:
subprocess.run(['systemctl', 'restart', 'nginx'])(Linux)、launchctl load /Library/LaunchDaemons/com.example.plist(macOS); - 读取其他用户进程信息:
subprocess.run(['ps', '-u', 'root']); - 修改系统配置文件:
open('/etc/hosts', 'w')或shutil.copy(..., '/usr/local/bin')。
这些不是“脚本写错了”,而是设计上就要求显式授权。
立即学习“Python免费学习笔记(深入)”;
不加 sudo 的安全替代方案有哪些?
优先用沙盒友好的等价逻辑,避免硬性提权:
- 查磁盘空间:用
shutil.disk_usage(Path.home())替代psutil.disk_usage('/'); - 找可执行文件位置:用
shutil.which('python')替代subprocess.run(['which', 'python'])(更轻量且不触发 shell); - 列出当前项目文件:用
os.listdir('.')或Path('.').iterdir(),别拼绝对路径; - 需要改系统配置?改到用户级位置:
~/.config/myapp/config.toml而非/etc/myapp/config.toml; - 若必须调用
sudo,确保命令白名单化,并在/etc/sudoers中用NOPASSWD限定具体命令,而非开放整个systemctl。
WorkBuddy 等受限沙盒环境要特别注意什么?
这类工具(尤其 macOS 上的 Electron 或 App Sandbox 应用)有额外限制:
- 即使你手动给了「完整磁盘访问」权限,
subprocess启动的子进程**不自动继承该权限**; - 脚本路径和所有涉及的文件(包括
pd.read_csv('data.csv')中的data.csv)**必须全部落在授权路径内**(如~/Documents),相对路径无法“穿透”沙盒; - 启动后必须**彻底重启 WorkBuddy 进程**(关掉托盘图标+活动监视器里杀干净),否则新权限不生效;
- 加
--debug参数才能看到真实被拒的路径,比如日志里出现Operation not permitted on /tmp/xxx,就说明有临时文件落到了禁区。
最常被忽略的一点:你以为只改了脚本路径就够了,但脚本里任何一次 open()、subprocess、pathlib.Path().resolve() 都可能悄悄越界——得逐行检查数据流终点。


















