crontab 脚本日志重定向:避免任务静默失败
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,是让定时任务从“静默失败”变成“可排查”的最低成本改动。