在 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 封额外邮件在哪里?
总结一下:
- 邮件在此系统上配置正确,我可以毫无问题地发送和接收邮件
/usr/bin/sendmail
。 - Cron 设置正确,按预期注意到任务并在配置的时间精确执行它。我尝试了许多其他任务和调度选项,并且 cron 完全按照预期执行了它们。
- 剧本总是写入输出(见下文),因此我们期望 cron 在每次调用时通过邮件将输出发送给我。
- 输出只是偶尔邮寄给我,并且在大多数情况下显然被忽略。
有很多方法可以解决导致上述观察结果的明显错误:
- 我可以
SHELL=/bin/bash
在我的crontab
. - 我可以创建一个
heartbeat.sh
with#!/bin/bash
,并调用它。 /bin/bash -c ...
我可以使用inside调用脚本crontab
。- 等等,所有这些都修复了在
sh
.
然而,所有这些都没有解决这个问题的核心问题,即在这种情况下,cron
即使脚本也不能可靠地发送邮件总是创建输出。
我已经验证该脚本始终通过创建来创建输出wrong.sh
(这又是故意使用不合适的/bin/sh
shell,产生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,但我的理论是:
- cron 没有捕获子 shell 的 stderr,并且
- 子 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
)。