Cron 仅偶尔发送有关输出和错误的电子邮件

Cron 仅偶尔发送有关输出和错误的电子邮件

在 Debian 8.1 上,我使用的是重击检测 stackoverflow.com 网站是否可访问的功能:

(回声>/dev/tcp/stackoverflow.com/80) &>/dev/null || echo“无法访问stackoverflow”

sh这是 Bash 特定的,在 . 默认 shell中不起作用cron

如果我们故意尝试 中的脚本sh,我们会得到:

$ /bin/sh: 1: cannot create /dev/tcp/stackoverflow.com/80: Directory nonexistent

因此,如果我只将以下内容放入我的个人 crontab (不设置SHELL/bin/bash) via crontab -e,我希望每分钟执行一次脚本,因此我希望每分钟每封邮件发送一次上述错误:

* * * * * (echo >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo“无法访问stackoverflow”

事实上,正如预期的那样,我们看到/var/log/syslog该条目每分钟执行一次:

#须藤 grep stackoverflow /var/log/syslog
8月24日18:58:01本地主机 CRON[13719]: (mat) CMD ((echo >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo "stackoverflow 无法访问")
8月24日18:59:01本地主机 CRON[13723]: (mat) CMD ((echo >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo "stackoverflow 无法访问")
8月24日19:00:01本地主机 CRON[13727]: (mat) CMD ((echo >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo "stackoverflow 无法访问")
...

在过去约 2 小时内,该命令已执行了 120 多次,我可以通过将输出管道传输到wc -l.

然而,从这些 >120 次 shell 命令(重复一遍:shell 命令对 无效/bin/sh)被执行以来,我只得到了电子邮件:

第一个在 19:10:01,第二个在 20:15:01,第三个在 20:57:01。

所有三封邮件的内容完全按照预期读取,并且完全包含在不兼容的 shell 中运行脚本所预期的错误消息(故意)。例如,我收到的第二封邮件内容如下(另外两封几乎相同):

[电子邮件受保护]  2015 年 8 月 24 日星期一 20:15:01
从:[电子邮件受保护](克朗守护进程)
到:[电子邮件受保护]
主题:Cron (echo >/dev/tcp/stackoverflow.com/80)&>/dev/null || echo“无法访问stackoverflow”
...

/bin/sh: 1: 无法创建 /dev/tcp/stackoverflow.com/80: 目录不存在`

/var/log/mail.log,我看到这三封邮件是唯一的过去几个小时内发送和接收的邮件。

因此,由于错误脚本创建的上述输出,我们预计从 cron 收到的超过 100 封额外邮件在哪里?

总结一下:

  1. 邮件在此系统上配置正确,我可以毫无问题地发送和接收邮件/usr/bin/sendmail
  2. Cron 设置正确,按预期注意到任务并在配置的时间精确执行它。我尝试了许多其他任务和调度选项,并且 cron 完全按照预期执行了它们。
  3. 剧本总是写入输出(见下文),因此我们期望 cron 在每次调用时通过邮件将输出发送给我。
  4. 输出只是偶尔邮寄给我,并且在大多数情况下显然被忽略。

有很多方法可以解决导致上述观察结果的明显错误:

  1. 我可以SHELL=/bin/bash在我的crontab.
  2. 我可以创建一个heartbeat.shwith #!/bin/bash,并调用它。
  3. /bin/bash -c ...我可以使用inside调用脚本crontab
  4. 等等,所有这些都修复了在sh.

然而,所有这些都没有解决这个问题的核心问题,即在这种情况下,cron即使脚本也不能可靠地发送邮件总是创建输出。

我已经验证该脚本始终通过创建来创建输出wrong.sh(这又是故意使用不合适的/bin/shshell,产生cron应该看到的相同错误):

#!/bin/sh
(回声>/dev/tcp/stackoverflow.com/80)&>/dev/null || echo“无法访问stackoverflow”

现在我可以循环调用脚本并查看是否有完成的情况没有创建输出。使用重击:

$ 当为真时;做 [[ -n $(./wrong.sh 2>&1 ) ]];回声 $?;完成 | grep -v 0

即使在数千次调用中,我也无法重现脚本完成而不创建输出的情况。

这种不可预测行为的原因可能是什么?任何人都可以复制这个吗?对我来说,似乎可能存在竞争条件,其中 cron 可能会错过脚本的输出,可能主要涉及错误源于 shell 本身的情况。谢谢你!

答案1

经过进一步测试,我怀疑这&会扰乱你的结果。正如你所指出的,&>/dev/null巴什语法,不句法。结果,sh创建了一个子 shell 并将其设置为后台。当然,子 shellecho会创建 stderr,但我的理论是:

  1. cron 没有捕获子 shell 的 stderr,并且
  2. 子 shell 的后台总是成功完成,从而绕过您的|| echo ....

...导致 cron 作业没有输出,因此没有邮件。根据我对 vixie-cron 源代码的阅读,似乎作业的 stderr 和 stdout 将被 cron 捕获,但它一定会被子 shell 丢失。

在 /bin/sh 环境中自行测试(假设这里没有名为“bar”的文件):

(grep foo bar) &
echo $?

答案2

我可以使用以下 crontab 在 Ubuntu 15.04 上重现该现象:

* * * * * { echo job 0; } & sleep 5
* * * * * { echo job 1; } &
* * * * * { sleep 5; echo job 2; } &

我每分钟都会收到来自 cron 的邮件job 0,邮件包含job 1 偶尔(最后 10 分钟内 5-6 次),没有带有job 2.

所以看起来 cron 会等待子进程的退出,然后发送一封邮件,其中包含当时可以吸收的所有 stdout/stderr 输出。孤立孙进程的延迟输出将被简单地丢弃。

答案3

除了上面的评论之外,我想知道是否可以有一个稍微简单的解释。

回想一下,任何 shell 在执行任何操作之前都会扩展/处理命令行。因此,当您使用sh“&”展开时,命令行会终止,并尝试将其置于后台(默认情况下优先级较低),并且不会根据重定向的内容正确分配 stdin/stdout/stderr。因此,“竞争条件”可能取决于 shell (sh) 处理该行的速度,这显然取决于系统上的负载(以及随后它如何与 交互cron)。

相关内容