cron の式の読み方
cron の式は空白で区切られた 5 つの値であり、左から順に、分、時、日、月、曜日と読みます。ジョブは、現在時刻がすべてのフィールドに一致したときに実行されます。
30 4 * * 1
│ │ │ │ │
│ │ │ │ └── day of week (1 = Monday)
│ │ │ └──── month (any)
│ │ └────── day of month (any)
│ └──────── hour (4am)
└─────────── minute (30)
この式は「毎週月曜の 04:30 に」と読みます。
5 つのフィールド
| # | フィールド | 使える値 | 備考 |
|---|---|---|---|
| 1 | 分 | 0 ~ 59 | |
| 2 | 時 | 0 ~ 23 | 24 時間制。0 は深夜 0 時 |
| 3 | 日 | 1 ~ 31 | |
| 4 | 月 | 1 ~ 12、または JAN ~ DEC | |
| 5 | 曜日 | 0 ~ 6、または SUN ~ SAT | 0 は日曜。ほとんどの cron 実装は 7 も受け付ける |
スケジューラーによっては形が違います。多くの Java ソフトウェアで使われている Quartz は、先頭に秒を置き、末尾に省略可能な年を加え、曜日を日曜始まりの 1 から 7 で番号付けします。フィールドが 6 つや 7 つある式を見たら、自分の読み方を信じる前に、どのシステム向けに書かれたものかを確認してください。
演算子
演算子は 4 つあり、1 つのフィールドの中で組み合わせて使えます。
| 記号 | 名前 | 例 | 意味 |
|---|---|---|---|
* | 任意 | * * * * * | 毎日、毎分 |
, | リスト | 0 9,13,17 * * * | 09:00、13:00、17:00 に |
- | 範囲 | 0 9-17 * * * | 09:00 から 17:00 まで、両端を含めて毎時 |
/ | ステップ | */15 * * * * | :00、:15、:30、:45 に |
ステップは常に範囲に対して適用されます。分のフィールドの */15 は「0 から始めて、59 までの範囲で 15 個おきに値を取る」という意味です。明示的な範囲にステップを付けることもできます。5-30/10 は 5、15、25 を選び、35 は終端を超えるのでそこで止まります。
ステップは「今から 15 分ごと」という意味ではありません。時計上の固定された値を選ぶものです。*/40 は :00 と :40 に発火し、その後は次の時の :00 まで 20 分しか待ちません。数え直しが 1 時間ごとに起きるからです。リストの中に範囲やステップを入れることもできるので、0 0-6/2,12,18-23 * * * の時のフィールドは妥当です。
名前とショートカット
月と曜日のフィールドは 3 文字の名前を受け付け、大文字と小文字は区別されないので、JAN、jan、Jan はすべて同じです。従来の crontab のドキュメントでは名前による範囲やリストは許可されないとされていますが、現代の実装の多くは MON-FRI を受け付けます。ジョブを動かしているものが何か確信を持てないなら、数値を使ってください。1-5 はどこでも曖昧さがありません。
5 つのフィールド全体を置き換えるショートカットもいくつかあります。
| ショートカット | 等価な式 | 実行タイミング |
|---|---|---|
@yearly または @annually | 0 0 1 1 * | 1 月 1 日の深夜 0 時 |
@monthly | 0 0 1 * * | 毎月 1 日の深夜 0 時 |
@weekly | 0 0 * * 0 | 日曜の深夜 0 時 |
@daily または @midnight | 0 0 * * * | 毎日深夜 0 時 |
@hourly | 0 * * * * | 毎正時 |
@reboot もありますが、これはスケジュールではありません。cron 自身が起動したときにジョブを 1 回実行します。
日と曜日の落とし穴
これが痛い目を見るところです。日のフィールドと曜日のフィールドの両方が制限されている場合、つまりどちらも * でない場合、cron は両方が一致したときではなく、どちらか一方 が一致したときにジョブを実行します。
0 0 13 * 5
これを「13 日の金曜日の深夜 0 時」と読みたくなるかもしれません。実際の意味は「毎月 13 日の深夜 0 時、および毎週金曜の深夜 0 時」です。年に 1 回か 2 回ではなく、60 回以上実行されます。
期待していた AND の挙動になるのは、2 つのフィールドのどちらかが * のときだけです。したがって 0 0 * * 5 は毎週金曜、0 0 13 * * は毎月 13 日であり、どちらもごく普通に動きます。OR が発動するのは、両方を制限したときです。
「13 日の金曜日」を 5 つのフィールドで表す標準的な方法はありません。一般的な回避策は、毎月 13 日にスケジュールしておき、ジョブ側で処理を始める前に曜日を確認させることです。
関連する細かい点があります。実装によっては、フィールドが制限されているかどうかを、その文字列が * で始まっているかどうかで判定します。そうした実装では、曜日フィールドの 0-6 は全曜日を覆っているにもかかわらず制限されているとみなされ、OR の挙動が静かに有効になります。「任意」を意味するときは * を使ってください。
確認しておくべき間違い
* */2 * * *は 2 時間おきの各時間帯の 毎分 実行され、1 日に 720 回になります。意図していたのは0 */2 * * *で、こちらは 12 回です。- 分のフィールドが抜けているとすべてが 1 つ左にずれますが、それでもたいていは解析が通ってしまい、まったく違う時刻の妥当なスケジュールができあがります。
0 0 31 * *は 31 日のない月を黙って飛ばします。- スケジューラー側で指定できない限り、cron はシステムまたはユーザーのタイムゾーンを使います。夏時間で時計がずれると、実装によっては、飛ばされた時間帯のジョブがまったく実行されなかったり、繰り返された時間帯のジョブが 2 回実行されたりします。1 日に 1 回必ず実行しなければならないジョブは、切り替えの起きる未明の時間帯を避けてスケジュールするのが安全です。
迷ったら、式を文章として読み上げ直し、その上で実際に次に発火する数回の日時を確認してください。その日時が自分の説明したものと違っているなら、間違っているのは読み方ではなく式のほうです。