手动跑得好好的脚本,一进 crontab 就报错:cron 的环境和你登录 shell 的环境不是一回事
现象
周六凌晨三点,备份脚本没有产出文件。SSH 上去手动执行 /opt/scripts/backup.sh,一秒跑完,日志里一切正常;crontab -l 里那行也没写错。于是整个周末都在怀疑磁盘满、权限、Python 依赖——其实都不是。
失败的那行日志只留下这么一句:
/opt/scripts/backup.sh: line 12: python3: command not found
python3 明明装在 /usr/local/bin/python3(自己编译装的),手动跑能找到,cron 跑就找不到。这就是最典型的判断依据:cron 用的是 PATH=/usr/bin:/bin 这个极简环境,它不读 /etc/profile、不读 ~/.bash_profile、不读 ~/.bashrc。你在 shell 里 echo $PATH 看到的那一长串,全是你登录时由 profile 脚本拼出来的,cron 完全看不到。
顺带说,这也是 cron 任务里 LANG、HOME、PYTHONPATH 缺失,中文文件名变成一串问号,以及 docker、nvm、conda 命令找不到的共同原因。
定位方法
不要猜,直接把 cron 的环境和自己的环境各打一份出来对比:
* * * * * env > /tmp/cron-env.txt 2>&1
env | sort > /tmp/login-env.txt
sort /tmp/cron-env.txt > /tmp/cron-env.sorted.txt
diff /tmp/cron-env.sorted.txt /tmp/login-env.txt | head -30
PATH、HOME、SHELL、LANG 这几行的差异,基本就能对上你遇到的每一个报错。
三种改法,按推荐顺序
1. 在脚本里自己声明环境(最稳,脚本还能单独手动跑)
#!/bin/bash
export PATH=/usr/local/bin:/usr/bin:/bin
export LANG=zh_CN.UTF-8
export PYTHONPATH=/opt/scripts/lib
cd /opt/scripts || exit 1
exec /usr/local/bin/python3 backup.py >> /var/log/backup.log 2>&1
关键点:脚本开头 cd 到绝对路径。cron 的工作目录是执行用户的家目录,脚本里任何相对路径(配置文件、日志、./data)在 cron 下都会指错地方,这也是「手动跑正常、cron 跑报找不到文件」的另一个高频原因。
2. 在 crontab 顶部统一声明(适合一个用户下很多任务)
PATH=/usr/local/bin:/usr/bin:/bin
LANG=zh_CN.UTF-8
SHELL=/bin/bash
MAILTO=""
30 3 * * * /opt/scripts/backup.sh
注意 crontab 里的变量赋值不支持 shell 语法:PATH=$PATH:/opt/bin 这种写法在这里无效,会原样当成一个名为 PATH 的字面值。
3. 直接写绝对路径(改动最小)
30 3 * * * /usr/local/bin/python3 /opt/scripts/backup.py
这种做法能解决「命令找不到」,但解决不了 PYTHONPATH、LANG 和相对路径的问题,适合只有单个脚本的简单场景。
一个容易漏掉的坑
把 2>&1 只写在命令末尾,邮件却没配、MAILTO 也没设,错误输出会被直接丢弃——于是你看到的就是「任务没跑,日志里什么都没有」。调试阶段先在命令后加 >> /tmp/cron-run.log 2>&1,确认稳定后再去掉。
另外,systemd 服务同理:它的环境既不来自登录 shell,也不来自 crontab,而是来自 unit 文件里的 Environment= 或 EnvironmentFile=,排查思路一致,只是要换地方改。
- 点赞
- 收藏
- 关注作者
评论(0)