crontabのガイドライン

cron (クーロン) と言えば定期実行してくれるおなじみのシンプルな機能だがそこには奥行きがあり様々なノウハウがある。

そこで設定するときのガイドラインをまとめた。
深くはあまり説明出来ないので気になる用語機能を見つけたら別途検索していただきたい。

# cronの場所

## /etc/crontab

crontabの書式
意識してここに書く必要は現在あまり無い。
RedHat系は非推奨とか言われてるが根拠はよくわからない。
rootで実行される。

```bash
#crontabの書式
# （行頭の # マークはコメント行を示す）
# +------------ 分 (0 - 59)
# | +---------- 時 (0 - 23)
# | | +-------- 日 (1 - 31)
# | | | +------ 月 (1 - 12)
# | | | | +---- 曜日 (0 - 6) (日曜日=0)
# | | | | |
# * * * * * 実行されるコマンド
```

## /var/spool/cron/

crontabの書式
`crontab -e`　でスケジュールしたときに格納される。
実行したいユーザ名で保存され600にする必要がある。
（ `crontab -e` すれば勝手に作られるので自分で作ることはほぼ無く意識する必要は無い）

crontabをバックアップするならこのディレクトリ下をバックアップする。

```bash
ls -l /var/spool/cron/
```

## /etc/cron.d/

crontabの書式
主にsystemが使う。意識してここに置く必要は現在あまり無い。
rootで実行される。

```bash
ls -l /etc/cron.d/
```

## /etc/anacrontab

anacrontabの書式
cronのようでcrontabじゃ無いanacrontab。
遅延実行、再起動時の未実行タスクの消化などしてくれる。
特にjobのシリアライズ制御があるのでcronにありがちな02時台のバックアップジョブが1時間を超えたので後続処理が死んだ！！！！！１１１１１みたいなことを回避することができる。
さらに言えばcron0時病（後述の実行時間は0分を避ける0時0分はもっと避ける）参照も気にしなくていい
rootで実行される。

```bash
less /etc/anacrontab
```

## /etc/cron.hourly/ /etc/cron.daily/ /etc/cron.weekly/ /etc/cron.monthly/

crontabとは別な前述のanacrontabの管轄
それぞれ毎時毎日毎週毎月実行する。
タイミングずらしをしてくれる。（前述のanacrontabの機能）
スクリプトを直接配置する。

```bash
ls -l /etc/cron.hourly
ls -l /etc/cron.daily
ls -l /etc/cron.monthly
```

# cronの書き方

## 実行パターン

**5コある米印がそれぞれ分時日月曜を表す。**

```bash
#分時日月曜
#* * * * * 実行されるコマンド
```

米印は任意の値であり
`* * * * *` なら毎分
数字を指定すればその時間に実行される。
`0 * * * *` なら毎時0分
`0 0 * * *` なら毎日0時0分
`0 0 1 * *` なら毎月1日0時0分
`0 0 1 1 *` なら毎年1月1日0時0分
~~`0 0 1 1 0` なら毎年1月1日0時0分が日曜日だったとき（6年に1回しか実行されない）~~ 嘘でした
曜日と日はOR条件となります
この場合
`0 0 1 1 0` なら毎年1月1日0時0分と日曜日の0時0分に実行されます
これは2022年なら以下の日にちで実行されます

```
2022-01-01 00:00:00 (1月1日）（土曜日)
2022-01-02 00:00:00 (日曜日)
2022-01-09 00:00:00 (日曜日)
2022-01-16 00:00:00 (日曜日)
```

### 米印以外にも書式があり、以下の制御文字が使える。

```bash
*	任意の値
,	値リストセパレータ
-	値の範囲
/	ステップ値
```

`0 0,12 * * *` なら0時0分と12時0分
`0 10-18 * * *` なら10時から18時までの0分
`0 */6 * * *` なら0時から6時間毎の0分
`0 6-18/6 * * *` なら6時から18時の間に6時間ごとの0分

### 組み合わせもできる

`0 0,10-18 * * *` なら0時と10時から18時までの0分
`0 10-18 * * *` なら10時から18時までの0分
`0 21,6-18/6 * * *` なら21時6時から18時の間に6時間ごとの0分

**ステップ値の取扱**

ステップ値の `*/数字` は0(または1)からのステップになる。
これは `* 0-23/6 * * *` と `* */6 * * *` が同義である。

特にこのステップ値はスケージュール登録する上で重宝する。
例えば営業時間中(9時から18時で休憩は13時から14時)に15分間隔ただし0分から5分ずらすといった場合、
`5-59/15 9-13,14-18 * * *`とすることができる。

1時間以内で終わる6種類ある各処理を同時起動しないように6時間ごとに5分に実行したい場合は

```bash
5 0-23/6 * * * a.sh
5 1-23/6 * * * b.sh
5 2-23/6 * * * c.sh
5 3-23/6 * * * d.sh
5 4-23/6 * * * e.sh
5 5-23/6 * * * f.sh
```

とすることができる。

なお
https://crontab.guru/
で次回起動が確認できるので便利

# パスを通す
crontabではパスが通ってないので、度々コマンドが無いと怒られる。
crontabの冒頭にPATHが指定すると読んでくれる。

```bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin/

5 3 * * * apachectl graceful
```

またパスを通すまでもないときは `which` でフルパスを探して書くと失敗は少ない。
`which apachectl` -> `/usr/sbin/apachectl`
`5 3 * * * /usr/sbin/apachectl graceful`


# crontabでのエスケープ

`0 * * * * /bin/bash echo '実行した' >> /var/log/log_$(date +%Y%m%d).log 2>&1` 
上記のように毎時/var/logにlog_年月日.log というファイルに標準出力・エラー出力を書き出すスケジュールを追加した場合うまく行かない。

正しくは
`0 * * * * /bin/bash echo '実行した' >> /var/log/log_$(date +\%Y\%m\%d).log 2>&1`
とする必要がある。

これは `%` がcrontabの制御文字と衝突するためエスケープが必要になるため。
なのでcrontabで `%` 使う場合は必ず `\%` とエスケープが必要。

エスケープ無しで `%` を使うと `%` より後ろすべてが標準入力とされてしまう仕様によるもの。
>``6 番目'' の欄 (行の残りの部分) には実行したいコマンドを指定します。行の 中のコマンド部全体、これは改行または % 文字までですが、は >/bin/sh かまたはその cron ファイルの SHELL 変数で指定されたシェルによって実行されます。コマンド内のパーセント記号 (%) はバックスラ>ッシュ (\) でエスケープされない限り改行文字に変換され、そ の最初の % 以降の全てのデータはそのコマンドの標準入力となります。
CRONTAB (5)より

# cronの実行ログは消さないこと
cronでは出力(標準出力/標準エラー出力)があった場合所有者にメールを出す。
大体は煩わしいので最終行に `>/dev/null 2>&1` と書かれることがある。

**しかしこれは非常に良くない**
定期実行されるcronで処理が失敗した場合手がかりがなくなってしまうことになる。
いつから失敗したのかなど根本的なところから何も得られない。
`>/dev/null 2>&1` を覚えるぐらいなら `>> /var/log/log_$(date +\%Y\%m\%d).log 2>&1` を覚えたほうが良い。
`2>&1` を毎度間違えると言うなら `>> /var/log/log_$(date +\%Y\%m\%d).log 2>> /var/log/log_error_$(date +\%Y\%m\%d).log ` でも良い

# crontabはバックアップしとくと安心

変更前はバックアップするのが鉄則
`crontab -l > ~/.crontab-editlog.bk`

ちら見する場合、 `-e` は使わず `-l` でみる。
大きいcrontabの場合は `crontab -l|less` とすると良い。

**編集するとき　`-r` と押し間違えるので `-e` を使うなと教えてるところが有るが逆に危険**
これはcrontabが構文チェックしてるので直接/etc/crontabに書かれると構文チェックが働かず間違った構文で保存するとエラーになる。そもそもroot権限なので権限が無いと話にならない。

どうしてもと言うなら `crontab [cronfile]` でcrontabに上書きする方法。
これなら構文チェックも効くのでうっかりミスも防げる。
この方式でもバックアップは別に取るべきだろう。でなければviで編集してしまうと元が消えてしまう。
さらに言えばこのcrontabファイルをgit管理しとけば変更履歴も追えて一石二鳥かもしれない。

ただ他人の環境を覗くとき完備された環境ではないので毎回 `crontab -l` したあと `crontab -e` する癖をつけたほうが良い。
そうすれば仮に間違えて `-r` したとしてもconsoleに残ってるのでリカバリが効くのでこの癖を推しときたい。

## バックアップを自動化する

バックアップは以下のcronを登録しておけば変更があるたびに勝手にバックアップを取るコマンド。
バックアップ頻度は起動時間で各自調整すると良いでしょう。

```bash
#CRON-BUCKUPv1
* * * * * crontab -l >~/.crontab-editlog$(date +\%Y\%m\%d\%H\%M).log && [ $(ls -t ~/.crontab-*.log 2>/dev/null |wc -l) -gt 1 ]  && diff $(ls -t ~/.crontab-*.log | head -1) $(ls -t ~/.crontab-*.log |head -2 |tail -1) >/var/tmp/cron-diff.log 2>&1 && rm -v $(ls -t ~/.crontab-editlog*.log | head -1) >>/var/tmp/cron-diff.log 2>&1
```

# 実行時間は0分を避ける0時0分はもっと避ける
anacrontabを使えという話ではありますが、結局いつ実行されてんねんと言う突上げがあったりして
結局crontabで良いかと持ってきてしまう事があるわけで、そうした場合に気をつけたいのがこの0分を避けること。
サブシステムが0分起動で集まっていると0分にメインシステムのロードアベレージが上昇してしまう事があります。ですのでちょっとずらす5分とか10分とかずらすと影響を避ける事ができます。（みんな5分に起動すると同じことになるのでそこは調整が必要）

# 最後に
ガイドラインと言いつつ脱線気味になってしまいましたが、各々のマイナールールがあると思うので絶対厳守というわけでは無いです。ただいざ自分が設定するとき懸念点を知っていれば楯突くことができるので失敗したときも良いといったやんけと言質を取ってマウントを取れるでしょう。細かく覚えなくてもなんか書いてあったなぁ程度頭に入れてもらえれば幸いです。

またこの例はRHEL系向けのもので、FreeBSDやdebianではコマンドの細部が異なる可能性があるので注意が必要です。おきをつけください。

以下のサイトを参考にしました。
https://crontab.guru/
https://www.server-memo.net/tips/etc-crontab.html
https://qiita.com/ohtsuka1317/items/b89ea66f3d227c172477
https://qiita.com/Gin/items/3514a2107e536223fba5
https://www.express.nec.co.jp/linux/distributions/knowledge/system/crond.html
https://qiita.com/takahashi-kazuki/items/77d669d4414c6ec185f9
https://bacchi.me/linux/cron-tips-2/
http://dqn.sakusakutto.jp/2012/06/shell_dev_null_2_1_crontab.html
https://qiita.com/ritukiii/items/b3d91e97b71ecd41d4ea