シェルスクリプトと組み合わせた自動化
定期実行では、1行のコマンドではなく複数の処理をまとめたシェルスクリプトを呼び出すのが定石です。スクリプト先頭に #!/bin/bash のシバンを書き、chmod +x で実行権を付けてからファイルとして登録します。何度動いても結果が変わらない(冪等な)作りにし、出力やエラーをログへリダイレクトしておくと、後から動作を追えて安全です。cron や timer からはこのスクリプトの絶対パスを指定します。
cron や systemd timer に登録する処理が、データベースを dump して tar で固めて別サーバへ scp で送り、古い世代を消す、のように複数の手順から成ることはごく普通です。これらを crontab の1行に && でつないで詰め込むこともできなくはありませんが、1行が長く読みにくくなるうえ、途中で失敗したときの処理が書けず、修正のたびに crontab を編集することになって扱いづらくなります。そこで定石となるのが、一連の処理を1つのシェルスクリプトファイルにまとめ、cron や timer からはそのスクリプトを呼ぶだけにする、という形です。処理の本体をファイルとして独立させることで、手元で何度でも実行テストでき、変更履歴をバージョン管理(git など)に乗せやすく、スケジュールと処理内容を分けて考えられるようになります。スケジューラはあくまで「いつ起動するか」だけを担当し、「何をするか」はスクリプトに任せる、という役割分担が見通しのよい自動化の基本形です。
シバンと実行権限
スクリプトファイルを作るときの作法が2つあります。1つ目は、ファイルの先頭行にシバン(shebang)を書くことです。#!/bin/bash のように #! に続けてインタプリタの絶対パスを書くと、このファイルを ./backup.sh のように直接実行したとき、どのプログラムで解釈・実行すべきかが決まります。bash 固有の機能(配列や [[ ]] など)を使わない汎用的なスクリプトなら、より移植性の高い #!/bin/sh とすることもあります。2つ目は、実行権限を付けることです。Linuxは拡張子ではなく実行権(x)の有無でファイルを実行できるかを判断するため、作ったばかりのスクリプトには chmod +x backup.sh で実行権を与えます。これを忘れて ./backup.sh を実行し「Permission denied」となるのは、初学者がほぼ必ず一度は通る道なので、覚えておくと回り道を減らせます。なお、cron からフルパスで bash /usr/local/bin/backup.sh のように明示的にインタプリタを指定して呼ぶ場合は、実行権が無くても動かせますが、どんな呼ばれ方でも動くよう実行権は付けておくのが無難です。
冪等性 — 何度走っても同じ結果に
定期実行されるスクリプトでは、冪等(べきとう、idempotent)であることをとくに意識します。冪等とは、同じ処理を何回実行しても、1回だけ実行したときと同じ最終状態になり、繰り返しても害が出ない性質のことです。auto-idempotent な作りにしておくと、cron が何らかの理由で二重に起動してしまったときや、動作確認のために手動でもう一度走らせたときにも、安心して任せられます。具体的には、ディレクトリ作成は既に在っても失敗しない mkdir -p を使う、処理の前にファイルの有無を if [ -f ファイル ] で確かめる、追記ではなく上書きで常に一定の状態を作る、といった工夫をします。逆に、実行のたびに同じ行を追記し続ける・連番を無条件に増やしていく、といった作りは冪等ではなく、繰り返すほど状態が膨らんだり壊れたりするので避けます。「もう一度動いても大丈夫か?」を一行ごとに自問しながら書くのが、自動化スクリプトの基本姿勢です。
堅牢にする定番のひと工夫
スクリプト冒頭に set -euo pipefail と書いておくのは、自動化では広く使われる安全策です。-e はコマンドが失敗(非ゼロ終了)したらそこで即座に止める、-u は未定義の変数を使ったらエラーにする、-o pipefail はパイプの途中のコマンドが失敗してもそれを見逃さない、という意味で、これらを付けると「途中で失敗したのに気づかず最後まで走り、中途半端で壊れた結果を残す」事故を防げます。あわせて、処理の節目に echo "backup start" のようにメッセージを出しておくと、後でログを読んだときに「どこまで進んで、どこで失敗したか」が一目で分かります。さらに、成功・失敗を呼び出し側へ正しく伝えるため、スクリプトの最後で適切な終了コード(成功なら exit 0、失敗なら非ゼロ)を返すことも意識します。systemd の service から呼ぶ場合は、この終了コードを見て成功・失敗が journal に記録されるため、終了コードを正しく返すことが監視のしやすさに直結します。
ログへのリダイレクトと cron からの呼び出し
cron や timer から動かす処理は画面に結果が出ないので、出力を必ずログに残します。リダイレクトの基本として、>> はファイルへの追記、> は上書き(既存内容を消して書き直す)、2>&1 は標準エラーを標準出力と同じ送り先へ合流させる指定です。これらを組み合わせ、コマンドの末尾に >> /var/log/backup.log 2>&1 と書けば、通常の出力もエラーメッセージも同じログファイルに時系列で貯まります。日付ごとにログを分けたいなら、スクリプト内で logfile=/var/log/backup-$(date +%F).log のように変数を組み立てて使う手もあります。最後に、cron や systemd の service からスクリプトを呼ぶときは、必ず絶対パスで指定します。たとえば crontab には 0 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1 のように書きます。cron は実行時のカレントディレクトリやPATHが対話シェルと異なるため、相対パスで書くと「手では動くのに cron では見つからない」という典型的な失敗に直結します。処理はスクリプトに集約し、呼び出しは絶対パスで、出力はログへ——この3点を守るだけで、自動化の安定度は大きく上がります。次の項では、その「手では動くのに cron では動かない」の正体である環境変数と PATH の問題を掘り下げます。