PAYATHON 2026

systemd 启动的 Python 脚本调用 yt-dlp 时提示 Permission denied,如何排查?

数据阿明

结论:先确认服务实际执行的是哪个文件

现有信息还不足以确定原因。终端运行成功、systemd 服务运行失败时,先对比两种环境的运行用户、PATH 和 yt-dlp 实际路径,再检查文件权限。

Permission denied 表示执行时遇到了权限拒绝,但不能说明服务找到的 yt-dlp 就是终端里的那个,也不能据此认定问题由升级到 64 位系统引起。

你的 service 没有设置 User=。放在 /etc/systemd/system/ 且由系统级 systemd 管理的服务,默认以 root 运行。因此,不能直接认定是 systemd 使用普通用户造成了权限不足。

1. 核实终端中的可执行文件路径

在能够正常运行命令的终端中执行:

command -v yt-dlp
type -a yt-dlp
python -c 'import sys; print(sys.executable)'

这些命令分别用于查看 yt-dlp 的命令解析结果、是否存在多个同名命令,以及当前 python 使用的解释器。

原问题中的路径是 /user/suntooth/bin/yt-dlp。/user/ 和 /home/ 是不同的目录,应以实际输出为准,不要直接将它改为 /home/suntooth/bin/yt-dlp。

如果 yt-dlp 是 alias 或 shell function,服务中的 /bin/sh 通常不会获得交互式终端里的这些定义,需要找到它最终调用的可执行文件。

2. 查看服务里的实际运行环境

可以临时在 ids.py 调用 yt-dlp 之前加入以下代码:

import os
import shutil
import sys

print("uid:", os.getuid(), flush=True)
print("python:", sys.executable, flush=True)
print("cwd:", os.getcwd(), flush=True)
print("PATH:", os.environ.get("PATH"), flush=True)
print("yt-dlp:", shutil.which("yt-dlp"), flush=True)

重启服务并查看日志:

sudo systemctl restart ids.service
sudo journalctl -u ids.service -b -n 100 --no-pager

比较服务日志和终端输出。如果 Python 解释器或 yt-dlp 路径不同,应先处理这个环境差异。

shutil.which() 可以辅助定位命令,但不能代替实际执行测试。尤其遇到权限问题时,还要检查相应的文件和目录。

3. 明确运行用户、工作目录和 Python 解释器

如果这项任务本来就应当以 suntooth 的身份运行,可以让 systemd 直接启动 Python,省去中间的 shell 脚本:

[Unit]
Description=IDs

[Service]
Type=simple
User=suntooth
WorkingDirectory=/home/suntooth/ids
ExecStart=/usr/bin/python3 /home/suntooth/ids/ids.py

[Install]
WantedBy=multi-user.target

使用前需要确认:

  • suntooth 对脚本、输入文件和输出目录具有所需权限。
  • /usr/bin/python3 是你准备使用的解释器,并且安装了脚本所需的依赖。如果终端使用的是虚拟环境,应将 ExecStart 改为该虚拟环境的 Python 绝对路径。

WorkingDirectory= 对应原脚本中的 cd。原配置中的 After=multi-user.target 可以移除,服务随 multi-user.target 启动不需要通过它来实现。

修改 service 文件后执行:

sudo systemctl daemon-reload
sudo systemctl restart ids.service
sudo systemctl status ids.service --no-pager

显式设置用户有助于理解运行环境,但不能保证解决当前的权限错误。

4. 在 Python 中使用确认过的绝对路径

确认 yt-dlp 可执行文件的位置后,建议直接使用绝对路径,并用参数列表调用:

import subprocess

# 替换为前面确认过的 yt-dlp 可执行文件绝对路径。
yt_dlp_path = "/实际路径/yt-dlp"

subprocess.run(
    [yt_dlp_path, "--version"],
    check=True,
)

先用 --version 验证服务能否启动程序。成功后,再逐项加入实际业务参数,例如 [yt_dlp_path, "参数1", "参数2"]。

这种调用不需要 shell=True,也不再依赖 shell 通过 PATH 查找 yt-dlp。绝对路径只能解决命令定位问题,文件缺少执行权限时仍会失败。

原命令中的 -U 是自更新选项,可能涉及写入可执行文件。排查启动故障时先用 --version,可以把能否执行和能否更新分开验证。

5. 绝对路径仍报错时,检查权限和挂载选项

将下面命令中的路径替换为已确认的真实路径:

ls -l /实际路径/yt-dlp
namei -l /实际路径/yt-dlp
findmnt -T /实际路径/yt-dlp -o TARGET,OPTIONS
file /实际路径/yt-dlp

检查以下几处:

  • 文件执行权限:服务用户需要有执行该文件的权限。若确认只是文件所有者缺少执行位,可由所有者执行 chmod u+x /实际路径/yt-dlp。
  • 父目录访问权限:访问文件需要对路径中的每一级目录具备搜索权限,即目录的 x 权限。namei -l 可以逐级查看。
  • 挂载选项:如果文件所在文件系统带有 noexec,仅增加文件执行权限通常无法解决问题。
  • 文件类型和解释器:如果 yt-dlp 是脚本,还要检查其 shebang 指向的解释器是否存在且可执行;如果是二进制文件,再确认它适用于当前系统架构。

不要直接使用 chmod -R 777。应根据检查结果只调整必要的权限。

如果这些检查都正常,再通过 sudo systemctl cat ids.service 查看是否存在额外的配置覆盖或服务隔离设置。终端和服务可能访问到不同的文件系统视图,应结合完整配置和日志继续定位。

备注:内容仅供参考。