03 / 指南
寫出不鬧意外的 Cron 表達式
五個欄位及其範圍、名稱與數字、日期欄位的「或」陷阱、週日是 0 還是 7、日光節約的空缺與重疊,以及部署前用未來執行預覽來驗證。
五個欄位,從左向右讀
經典 Cron 表達式是五個以空格分隔的欄位:分鐘(0–59)、小時(0–23)、日(1–31)、月(1–12)、星期(0–7)。每個欄位接受單一值、逗號分隔的清單、連字號範圍、斜線步進,或表示所有值的星號。
大多數 Cron 缺陷並非罕見語法,而是普通欄位被粗心地讀錯:例如在範圍邊界重置的步進,或在按 23 小時計數的系統裡按十二小時制寫下的小時欄位。
- 清單清單挑選離散值:分鐘欄位裡的 0,30 表示整點和半點各觸發一次。
- 範圍與步進範圍挑選一段區間:小時欄位裡的 9-17 涵蓋工作時間,步進可以把區間抽稀——9-17/2 表示區間內每兩小時一次。
- 星號匹配全部星號不是「忽略此欄位」;它主動匹配所有值,一旦兩個日期欄位相互作用,這一點就變得關鍵。
名稱是附文件說明的數字
月份與星期欄位接受三字母英文名稱——JAN 到 DEC、SUN 到 SAT——作為數字的別名。0 9 * JAN MON 與 0 9 * 1 1 是同一個排程,但名稱版能向下一位讀者自我解釋,也更抗「差一」式修改。
- 不區分大小寫名稱解析不區分大小寫——mon、Mon、MON 都可以——但大寫是顯得有意的慣例。
- 範圍裡的名稱名稱也能用在範圍與清單裡:MON-FRI 是標準的工作日區間,JAN,APR,JUL,OCT 選出季度月份。
- 讀解釋Cron 建立與說明工具會正規化表達式並逐欄位解釋——「month: every value」「weekday: 1, 2, 3, 4, 5」——讓你確認解析器按你的本意讀懂了名稱。
兩個日期欄位是「或」關係
這是 Cron 最昂貴的意外:當「日」和「星期」兩個欄位都被限定——都不是星號——排程在任一匹配時就會觸發。0 9 13 * FRI 不是「13 號星期五」,而是每月 13 號和每個星期五。
說明工具的輸出對此很明確:它把語意標註為 dom-dow-or 並連同計算出的執行一起展示,於是一個會比預期觸發頻繁得多的表達式,會在進入 crontab 之前就露出馬腳。
- 一個萬用字元很簡單只要任一日期欄位是星號,另一個就單獨做主——這正是多數人以為處處成立的直覺行為。
- 都限定就是「或」兩者都限定意味著「或」:經典 Vixie cron 語意(多數 Unix 排程器沿用)在日期匹配或星期匹配時都會執行任務。
- 「與」需要變通要真正命中「13 號星期五」,讓 cron 選中 13 號,再讓命令本身檢查星期——或者換一個有「與」語意的排程器,經典 cron 不是。
週日是 0,通常也可以是 7
星期欄位把週日編號為 0,週一為 1,依此到週六為 6。多數實作還接受 7 作為第二個週日,照顧從週一開始數的人——Toolars 解析器內部會把 7 正規化為 0——但有些系統只接受其一,別處還存在以週一為 1 的 ISO 式編號。
- 0 與 7在接受 7 的地方,0 和 7 都是週日;寫 0 是跨排程器的可攜選擇。
- 悄悄的差一在拒絕 7 的系統上寫 7 會在部署時響亮地失敗;但把星期誤讀為「從週一起數的第七天」則會在錯誤的那一天悄悄失敗。
- 逐平台再確認當表達式在實作之間遷移——系統 crontab、容器平台、CI 排程器——請重新確認星期編號,而不是假設它安全抵達。
日光節約會扭彎排程
Cron 按排程器所在時區的時鐘時間觸發,而時鐘每年有兩次不守規矩。春季撥快的空檔裡,02:30 這樣的本地時間根本不存在;秋季撥回時它又存在兩次。釘在這一分鐘的排程會在三月漏跑一次,還可能在十一月跑兩次。
- 消失的一小時春季空檔:切換之夜的 02:30 任務沒有可匹配的時鐘時間,於是什麼都不觸發——沒有錯誤,沒有重試,只是少了一次執行。
- 翻倍的一小時秋季重疊:同一時鐘時間出現兩次,各實作對於任務跑一次還是兩次並不一致;除非你驗證過,否則按兩次來假設。
- UTC 沒有日光節約乾淨的逃生口是 UTC:它沒有日光節約切換,30 2 * * * 在一年裡的每一天都是同一時刻。說明工具提供 UTC、本機時區,以及 America/New_York、Europe/London 等具名時區。
用未來執行驗證,不靠運氣
正確的表達式與看起來像樣的表達式之間,差著一份具體的觸發時刻清單。Cron 建立與說明工具從你選擇的時區出發計算未來十次執行,最遠搜尋 366 天,並在背景工作執行緒中求值,再重的表達式也不會卡住分頁。
- 讀執行清單對著意圖讀頭幾次執行:「工作日 09:00」應該列出週一到週五的九點;清單裡出現一個週六,就是「或」陷阱在自報家門。
- 跨一次切換有意跨過日光節約邊界:在具名時區裡挑一個三月或十一月的日期,確認時鐘撥動時排程仍如你所願。
- 明確的錯誤失敗是明確的:非法欄位、欄位數錯誤、或無法在搜尋窗口內湊出十次執行的排程,各自得到具名錯誤,而不是一份悄悄空掉的清單。
排程與正規表示式共享同一套紀律。
兩者都是破壞半徑遠超體量的小字串——學習正規表示式的編寫—測試—重構循環,在帶逾時的本地工作執行緒中執行。