crontab 脚本不写日志?我把 7 个原因在 Ubuntu 上逐个复现了一遍

定时任务最让人抓狂的一类问题:crontab -l 里明明有这条任务,脚本手动跑完全正常,日志文件就是空的------甚至连文件都没生成。

我把「日志没写」拆成 7 个具体原因,每个都在 Ubuntu 24.04 + cron 3.0 上实测复现过,不是凭印象写的。

第 0 步:先确认 cron 到底有没有触发

不要一上来就改脚本。先在系统日志里搜一次:

bash 复制代码
# Debian / Ubuntu
sudo grep CRON /var/log/syslog | tail -20

# CentOS / RHEL(默认走 journald)
sudo journalctl -u crond --since "10 min ago"

看到这样的行,说明 cron 已经调度了:

bash 复制代码
CRON[1031]: (jsx13) CMD (echo $(date +)

注意上面这行本身就有问题------命令被截断了,原因见第 4 条。

如果一行都没有,别怀疑脚本,问题在调度侧:表达式写错、cron 服务没启动,或者跑在容器 / WSL 里而实例已经休眠。

坑 1:输出被 cron 直接丢弃

这是最常见的一条。cron 执行任务时,会把 stdout 和 stderr 收集起来,通过本地邮件发给用户。服务器没装 MTA(邮件服务)时,这些输出就被扔掉了。

实测的 syslog 记录(跑了 80 秒,出现 32 次):

scss 复制代码
(CRON) info (No MTA installed, discarding output)

意思是:脚本确实跑了,输出确实产生了,然后被扔了。你去看日志文件,当然什么都没有。

两种修法,建议都做:

bash 复制代码
# 1. crontab 行尾加重定向(捕获 cron 执行层输出,含 Permission denied 这类报错)
0 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

# 2. 脚本内部也重定向(保证脚本自己的逻辑输出不丢)
exec >>/var/log/backup.log 2>&1

exec >>file 2>&1 放在脚本开头,之后整个脚本的所有输出都会自动追加进文件,不用在每个命令后面写重定向。

坑 2:PATH 不全,命令根本找不到

cron 不会加载你的 .bashrc,它的 PATH 是守护进程继承来的,比登录 shell 窄得多。

实测对比(同一台机器):

javascript 复制代码
cron 的 PATH:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

登录 shell 的 PATH:
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games
:/usr/lib/wsl/lib:/mnt/d/soft/...(几十个自定义路径)

脚本里 which node 在 cron 下返回 none,在登录 shell 里能找到。同理,docker、kubectl、nvm 装的 node、~/.local/bin 里的工具,在 cron 下全部不存在。

典型症状 :脚本第 3 行就退出,日志里只有一句 command not found------而这句话又被坑 1 吞掉了。

修法:

bash 复制代码
#!/bin/bash
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

坑 3:工作目录不是脚本目录

cron 执行任务时,工作目录是用户的家目录,不是脚本所在目录。

实测:

bash 复制代码
CWD=/home/jsx13
cat: ./config.ini: No such file or directory

脚本里写 ./config.ini、./logs/app.log、../data/xxx,全部失效。更隐蔽的是:日志「看起来没写」,其实写到了 $HOME/logs/ 下面。

修法:

bash 复制代码
cd "$(dirname "$0")" || exit 1

坑 4:crontab 里的 % 没有转义

cron 最经典的坑,也最容易漏。crontab 把 % 当作换行符 ,第一个未转义的 % 之后的内容会被当成标准输入,命令本身被截断。

实测写了这样一行:

bash 复制代码
* * * * * echo $(date +%F) >> /tmp/today.log 2>&1

syslog 里的实际执行记录是:

bash 复制代码
CRON[1031]: (jsx13) CMD (echo $(date +)

命令在 % 处被切断,F) >> /tmp/today.log 变成了标准输入。任务跑了、报错了、日志文件压根没生成。

修法:% 写成 %。

bash 复制代码
0 3 * * * echo $(date +%F) >> /var/log/backup/daily.log 2>&1

date 的格式化串在 crontab 里几乎必然出现,所以这条命中率极高。

坑 5:脚本没有执行权限

chmod +x 忘了做,cron 直接调用脚本时会报 Permission denied。

实测:一个 644 权限的脚本,cron 调用后什么都没发生。手动执行才看到报错:

复制代码
Permission denied

因为报错走 stderr,又被坑 1 吞掉了,所以表面上「静默失败」。

修法:

bash 复制代码
chmod +x /opt/scripts/backup.sh

还有个容易忽略的:目录本身也需要 x 权限。

坑 6:shebang 带了 Windows 换行

脚本在 Windows 上编辑过、或用 FTP / SMB 拷进去,文件可能带 CRLF 换行。第一行变成 #!/bin/bash\r,内核找不到这个「解释器名带回车」的路径。

实测报错:

lua 复制代码
cannot execute: required file not found

如果直接 bash script.sh 执行,则会在后面某行报更奇怪的错:

arduino 复制代码
line 2: 1: ambiguous redirect

因为 \r 混进了重定向语法。

修法:

bash 复制代码
sed -i 's/\r$//' script.sh
# 或者
dos2unix script.sh

确认是否带 CRLF:

bash 复制代码
file script.sh                          # 出现 "with CRLF line terminators" 就是有问题
head -1 script.sh | od -c | head -2     # 能看到 \r \n

坑 7:cron 根本没跑起来

尤其容易出现在开发机的 WSL 或容器环境:

  • 服务没启动 :systemctl status cron,没 active 就 sudo systemctl enable --now cron
  • WSL 实例休眠:VM 在没有进程时被回收,cron 跟着暂停。测试时要保持一个进程让实例活着
  • 表达式本身错了:段数对不上(Linux 5 段 vs Quartz 6 段)、字段越界,cron 会静默跳过。用在线 cron 解析工具核对一下未来 5 次执行时间

排查顺序:5 分钟内定位

  1. 看 syslog 有没有 CMD (...) :没有 → 问题在调度;有但命令被截断 → 坑 4
  2. 看 syslog 有没有 No MTA installed:有 → 坑 1,输出被丢了
  3. crontab 行尾加 >> /tmp/x.log 2>&1 再等一轮,能看到 Permission denied、command not found 这类真实报错
  4. 用 cron 的环境手动跑一遍(最有效):
bash 复制代码
sudo -u app env -i HOME=/home/app PATH=/usr/bin:/bin SHELL=/bin/sh \
  /bin/sh -c "/home/app/bin/backup.sh"

env -i 清空所有环境变量,只给最小 PATH------和 cron 环境基本一致。能复现就在本地调。

生产可用的写法

crontab:

perl 复制代码
# 分 时 日 月 周   命令(路径全绝对 + 行尾重定向)
0 3 * * * /opt/app/bin/backup.sh >> /opt/app/logs/backup.log 2>&1

脚本:

bash 复制代码
#!/bin/bash
set -euo pipefail
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
cd "$(dirname "$0")" || exit 1
exec >>"$HOME/logs/run.log" 2>&1

echo "[$(date +%F\ %T)] start"
# ... 业务逻辑
echo "[$(date +%F\ %T)] done"

四件事一次做完:PATH 补全、目录锁定、输出落文件、出错即停。这样即使出问题,日志里一定有痕迹。

延伸阅读

同一批实测过的内容还有:Bash 调试 10 招(set -x 怎么局部开关)、Cron 表达式段数与周字段差异(Linux / Spring / Quartz)。


原文:dxtool.cn/blog/cronta...

相关推荐
广目软件6 小时前
HA分布式架构采集节点变更操作手册20261029
运维·服务器·ansible·zabbix·prometheus
米优6 小时前
linux下获取崩溃程序的core文件
linux·运维·服务器
半杯咖啡半行码6 小时前
QML进阶指南:状态动画与模型视图实战详解
运维·qml
qeen876 小时前
【Linux】动静态库的制作使用及ELF文件的宏观理解
linux·运维·库
xie881018026 小时前
正则里的点号不匹配换行:多行模式的开关
运维·服务器·测试工具
是jin奥6 小时前
Claude Code + GitHub 自动化工作流整理
运维·自动化·github
wefg16 小时前
【Docker】基本指令
运维·docker·容器
日常筹谋记7 小时前
数据中心逆变设备运维|通讯对接、动环接入与多系统联动方案科普
运维·网络
强哥聊算力7 小时前
《算力江湖·Intel帝国往事》第三集:安迪·格鲁夫的铁血时代:Intel为什么能把对手逼到墙角?
运维·人工智能·科技·算法·ai·gpu算力