🍐はじめに
cron
を使わずとも systemd を使ってジョブを定期的に実行できる。
一般的なLinuxディストリビューションは systemd を使っているため、こちらの利用方法を備忘録しておく。
今回やる systemd による定期実行については、
-
実行したいジョブを systemd サービスとして設定
-
systemd.timer から上記サービスを実行
とすればいい。
🐕🦺 systemd サービスとして実行ジョブを設定
-
実行するジョブをスクリプトにする
-
上記スクリプトを実行する systemd.service を作成
今回作成する systemd.service は systemd のユーザーモードで動かす(systemctl --user <subcommand> )ことを前提にする。 |
ジョブをスクリプトとして作成
systemd.service にコマンドを直接記述する場合はいくつか注意すべき点がある[1]。
よって、実行したいジョブはあらかじめシェルスクリプトにしておくことを推奨。
mkdir -p ~/.local/bin (1)
tee ~/.local/bin/hello.sh <<'EOF'
#!/bin/bash
echo "Hello," "$@" "!"
EOF
chmod u+x ~/.local/bin/hello.sh (2)
1 | systemd ユーザーモードで使うため、スクリプトは ~/.local/bin に配置した。 |
2 | 実行権限を与えることを忘れずに。 |
直接コマンドを記述する場合は、こういうことに注意する。
systemd.service Exec
シェル構文は変数の展開(
|
systemd.service Exec
コマンドが絶対パスで与えられていない場合、 systemd がコマンドを検索するパスの確認
|
systemd サービスを作成
systemctl edit --user --force --full demo@.service
[Unit]
Description=Demo service
[Service]
Type=oneshot
ExecStart=%h/.local/bin/hello.sh %i (1)
[Install]
WantedBy=multi-user.target
1 | ユニット指定子を利用している。%h → このサービスを実行したユーザーのホームディレクトリパス。%i → インスタンス名(ユニット名の @ 記号から拡張子までの間にある文字列)。
|
systemctl --user start demo@sample.service
systemctl --user status demo@sample.service
● demo@sample.service - Demo service
Loaded: loaded (/home/hoge/.config/systemd/user/demo@.service; disabled; vendor preset: enabled)
Active: inactive (dead)
Oct 23 22:12:38 raspberrypi systemd[747]: Starting Demo service...
Oct 23 22:12:38 raspberrypi hello.sh[15180]: Hello, sample !
Oct 23 22:12:38 raspberrypi systemd[747]: demo@sample.service: Succeeded.
Oct 23 22:12:38 raspberrypi systemd[747]: Started Demo service.
ユニットファイルの拡張子(.service や .socket )の直前に @ 記号をつけるとテンプレートユニットになる。引数を与えてユニットファイルの挙動を一部変更させたいときに便利。 |
⏰ systemd.timer を使って定期的に systemd.service を実行
systemctl edit --user --force --full demo@schedule.timer
[Timer]
OnCalendar=*-*-* *:00/10:00 (1)
AccuracySec=1m (2)
#Unit=demo@sample.service (3)
1 | 10分毎に定期実行。書式については後述。 |
2 | 精度。ここの例では指定した時刻から1分以内のどこかで実行されることになる。 小さくすればするほどCPU負荷が高くなるので注意(デフォルト値は1分)。 |
3 | この timer で実行するユニットを指定する(本例ではコメントアウト)。 指定がなければ timer と同名の service を実行する。 |
systemctl --user start demo@schedule.timer
systemctl --user status demo@schedule.timer
● demo@schedule.timer
Loaded: loaded (/home/suzutsuki/.config/systemd/user/demo@schedule.timer; static)
Active: active (waiting) since Sun 2021-10-24 00:05:15 JST; 9s ago
Trigger: Sun 2021-10-24 00:10:00 JST; 4min 35s left
Triggers: ● demo@schedule.service
Oct 24 00:05:15 raspberrypi systemd[747]: Started demo@schedule.timer.
journalctl --user --unit demo@schedule.service
Oct 24 00:10:22 raspberrypi systemd[747]: Starting Demo service...
Oct 24 00:10:22 raspberrypi hello.sh[16109]: Hello, schedule !
Oct 24 00:10:22 raspberrypi systemd[747]: demo@schedule.service: Succeeded.
Oct 24 00:10:22 raspberrypi systemd[747]: Started Demo service.
Oct 24 00:20:22 raspberrypi systemd[747]: Starting Demo service...
Oct 24 00:20:22 raspberrypi hello.sh[16125]: Hello, schedule !
Oct 24 00:20:22 raspberrypi systemd[747]: demo@schedule.service: Succeeded.
Oct 24 00:20:22 raspberrypi systemd[747]: Started Demo service.
Oct 24 00:30:22 raspberrypi systemd[747]: Starting Demo service...
Oct 24 00:30:22 raspberrypi hello.sh[16133]: Hello, schedule !
Oct 24 00:30:22 raspberrypi systemd[747]: demo@schedule.service: Succeeded.
Oct 24 00:30:22 raspberrypi systemd[747]: Started Demo service.
Oct 24 00:40:22 raspberrypi systemd[747]: Starting Demo service...
作成した systemd.timer が期待通り動作するようならOK。
あとは自動起動を有効化しておく。
systemctl --user enable demo@schedule.timer
😎👍
🖊️補足事項
OnCalendar の値について
systemd.time(7) — Arch manual pages を参考。
書式としては YYYY-MM-DD hh:mm:ss
の形になる(曜日指定もできるけど割愛)。
この値についての検証は systemd-analyze calendar
コマンドを利用する。
systemd-analyze calendar '4h'
Failed to parse calendar specification '4h': Invalid argument
systemd-analyze calendar '00,04,08,12,16,20:15:00'
Original form: 00,04,08,12,16,20:15:00
Normalized form: *-*-* 00,04,08,12,16,20:15:00
Next elapse: Sun 2021-10-24 00:15:00 JST
(in UTC): Sat 2021-10-23 15:15:00 UTC
From now: 1h 58min left
記号 | 説明 | 使用例 | |
---|---|---|---|
|
ワイルドカード |
*-*-* *:30:00 |
時刻が30分を指すたびに実行。 |
|
複数指定 |
*-*-10,20 00:00:00 |
10日と20日の午前0時に実行。 |
|
範囲指定 |
*-*-05..10 12:00:00 |
5日から10日にかけて、12時に実行。 |
|
繰り返し間隔 |
*-*-* *:00/15:00 |
15分ごとに実行。 |
|
月末最終日から |
*-10~03 00:00:00 |
10月末から数えて3日目、 |
システム終了時に systemd.service を実行したい場合
shutdown.target
前に実行される systemd.service として実装すればいい(systemd.timer は使わない)。
この方法では電源喪失(停電など)に対応できないことに注意。 通常のシャットダウン操作が実行された時のみ有効となる。 |
sudo systemctl edit --force --full run-on-shutdown.service
[Unit]
Description=Run scripts on shutdown
Before=shutdown.target (1)
DefaultDependencies=no (2)
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/local/bin/run-on-shutdown.sh
[Install]
WantedBy=shutdown.target (3)
1 | シャットダウンする前にこのサービスを実行させる。 |
2 | 起動初期やシャットダウン時に実行させる場合は no にしたほうがいいらしい。 |
3 | シャットダウン時に実行させるための指定。 |
sudo systemctl daemon-reload
sudo systemctl enable run-on-shutdown.service
📖参考
bash
の環境変数が使えないとか変数展開がうまくいかないとか。