crontab 里的任务失败时,最麻烦的不是报错,而是什么都不报。

典型场景是:写了个备份脚本挂到 crontab,每天凌晨跑一次。某天发现备份文件没更新,去看终端,没有任何输出;去看系统日志,也没有线索。任务其实已经失败好几周了,只是没人知道。

为什么会这样

cron 执行任务时没有终端。脚本里的 echo、命令自身的报错、set -e 触发退出前的提示,都写到了标准输出(stdout)和标准错误(stderr)这两个流上。

没有终端接收,cron 默认会尝试把这些输出通过本地邮件发给任务所有者。但很多机器没装邮件服务,或者邮件没人看,结果就是输出被直接丢弃。

所以“安静”不是任务没跑,而是它的所有话都被扔掉了。

怎么做

在 crontab 条目末尾加上重定向,把两个流都追加到文件里:

# 每天 3:10 执行备份脚本,输出追加到日志文件
10 3 * * * /opt/scripts/backup.sh >> /var/log/backup.log 2>&1

这行在做什么:>> 把标准输出追加到 /var/log/backup.log;2>&1 表示“把文件描述符 2(stderr)指向文件描述符 1 当前指向的地方”,也就是同一个日志文件。

两个符号的选择有讲究:

  • 用 >> 而不是 >。> 每次覆盖,日志只留最后一次运行的内容;追加才能看到历史。
  • 2>&1 必须写在 >> 之后。顺序反了,stderr 会指向重定向之前的 stdout(也就是终端),错误信息仍然丢失。

如果还想在日志里区分是哪次运行,可以在脚本开头自己打一行时间戳:

#!/bin/bash
echo "=== $(date '+%F %T') start ==="

怎么确认生效

先手动跑一遍,确认脚本本身没问题:

/opt/scripts/backup.sh >> /var/log/backup.log 2>&1

然后看日志文件是否被写入:

tail -n 20 /var/log/backup.log

再等一次 cron 触发,或者临时把时间改成几分钟后测试。日志里应该出现新的一段内容。

如果日志文件是空的,先检查权限:cron 以任务所有者身份运行,该用户必须对日志所在目录有写权限。用 ls -l /var/log/backup.log 看属主,必要时 chown 或换一个用户可写的路径,比如 $HOME/logs/。

边界和坑

日志会无限增长。 追加模式不会自动清理,长期运行要配 logrotate,或者在脚本里自己做大小判断。否则某天磁盘写满,问题比没有日志更大。

重定向只捕获脚本进程自己的输出。 脚本里再启动的后台进程,如果它自己没做重定向,输出仍然会丢。需要的话在脚本内部对子进程单独处理。

密码和密钥不要打进日志。 调试时容易顺手 set -x,把命令行参数连同 token 一起写进文件。日志目录的权限也要收紧,别让所有用户可读。

cron 的环境变量和登录 shell 不同。 PATH 往往更短,脚本里用到的命令可能找不到,报错 command not found 会进日志——这正是加日志的价值,能立刻看到这类问题。

一句话:给每个 crontab 条目加上 >> 日志路径 2>&1,是让定时任务从“静默失败”变成“可排查”的最低成本改动。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注