Linux

Cron 只是偶爾發送關於輸出和錯誤的電子郵件

  • May 23, 2019

在 Debian 8.1 上,我使用Bash功能來檢測是否可以訪問 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該條目每分鐘執行一次:

# **sudo grep stackoverflow /var/log/syslog** 
8 月 24 日**18:58:01** localhost CRON[13719]: (mat) CMD ((echo >/dev/tcp/stackoverflow.com/80) &>/dev/null ||迴聲“堆棧溢出無法訪問”)
8 月 24 日**18:59:01** localhost CRON[13723]: (mat) CMD ((echo >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo "stackoverflow unreachable")
8 月 24 日**19:00:01** localhost CRON[13727]: (mat) CMD ((echo >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo "stackoverflow unreachable")
...

在過去的約 2 小時內,這已經執行了 120 多次,因為我可以通過管道將輸出傳輸到wc -l.

但是,從這些 >120 次的 shell 命令(重複一遍:shell 命令對 無效/bin/sh)中,我只收到了封電子郵件:

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

所有三封郵件的內容都完全按照預期讀取,並且完全包含在不兼容的 shell 中執行腳本時預期的錯誤消息(故意)。例如,我收到的第二封郵件是這樣寫的(另外兩封幾乎相同):

來自 mat@myhost.com 2015 年 8 月 24 日星期一 20:15:01
來自:root@myhost.com(Cron 守護程序)
至:mat@myhost.com
主題: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/sh外殼,以產生cron應該看到的相同錯誤):

**#!/bin/sh**
(迴聲 >/dev/tcp/stackoverflow.com/80) &>/dev/null || echo "stackoverflow 無法訪問"

現在我可以在循環中呼叫腳本,看看是否有過在沒有創建輸出的情況下完成的情況。使用重擊

$雖然是真的;做 [[ -n $(./wrong.sh 2>&1 ) ]]; 迴聲$?;完成 | grep -v 0

即使在數千次呼叫中,我也無法重現腳本在沒有創建輸出的情況下完成的情況。

這種不可預測的行為可能是什麼原因造成的?任何人都可以重現這個嗎?對我來說,看起來可能存在 cron 可能會錯過腳本輸出的競爭條件,可能主要涉及錯誤源於 shell 本身的情況。謝謝!

經過進一步測試,我懷疑這&會影響您的結果。正如您所指出的,&>/dev/nullbash語法,而不是sh語法。因此,sh正在創建一個子外殼並將其作為背景。當然,子shellecho會創建stderr,但我的理論是:

  1. cron 沒有擷取 subshel​​l 的 stderr,並且
  2. 子shell 的後台處理總是成功完成,從而繞過你的|| echo ....

…導致 cron 作業沒有輸出,因此沒有郵件。根據我對 vixie-cron 原始碼的閱讀,似乎作業的 stderr 和 stdout 會被 cron 擷取,但它一定會被 subshel​​l 失去。

在 /bin/sh 環境中自己測試它(假設您在這裡沒有名為 ‘bar’ 的文件):

(grep foo bar) &
echo $?

引用自:https://unix.stackexchange.com/questions/225219