Macで定期実行を仕込むならlaunchd一択です。この記事では、launchdでMacの面倒な定期実行コマンドを自動化する全手順を、5つのステップに分けて解説します。
私はブログのバックアップ、依存パッケージの更新チェック、毎月のスクリプトなど、気づけば手作業で回すコマンドが20本以上ありました。正直めんどくさいし、忘れると地味にダメージがでかい。そこで本気で使い倒しているのがmacOS標準のlaunchdです。cronじゃなくてlaunchd。ここ、重要です。
読み終える頃には、あなたのMacも勝手に仕事をしてくれる「小さな秘書」に化けているはずです。
Macのlaunchdで自動化するメリットとcronとの違い
結論から言うと、Macで定期実行を仕込むならlaunchd一択です。cronでも動きはしますが、macOS Catalina(2019年)以降はセキュリティまわりの制約が強くなっていて、普通に書いたcronジョブがフルディスクアクセスで弾かれるケースがよくあります。最新のmacOS SequoiaやSonomaでもこの制約は続いています。
私も昔「なんで動かないの?」と丸一日溶かしました。launchdはmacOS自身が面倒を見てくれる仕組みなので、最初から筋が良いです。
そもそもlaunchdとは?cronが非推奨な理由
launchdはmacOSの起動直後から動いている常駐プロセスで、各種デーモンやユーザーエージェントを束ねる親玉のような存在です。PID 1で動いていて、OSの中心にいる、と言えば伝わるでしょうか。
cronは歴史の長いUNIX資産ですが、Appleは公式にlaunchdを推奨しています。sleep復帰やログイン連動など、Macらしい挙動を拾えるのはlaunchdだけです。素直に長いものに巻かれるのが吉です。
launchd自動化で得られる3つのベネフィット
私の体感で一番大きいのは「忘れなくなる」部分です。毎朝9時にブログ記事をエクスポートする、日曜の深夜に依存パッケージを監査する——こうした地味だけど大事なタスクをMacに預けた結果、手作業がほぼゼロになりました。節約した時間で記事を1本書けるし、なにより「やり忘れた罪悪感」が消えるのがメンタルに効きます。
launchd自動化の前に準備する3つのもの
launchdは強力な反面、ちょっとお作法にうるさいタイプです。いきなりplistを書き始めると、配置先で迷ったり、ログが出なくて詰んだりします。最初に3つだけ整理しておくと、あとの工程がびっくりするほどスムーズになります。
私が初めて設定したときに「先に知りたかった…」と後悔したポイントだけ厳選しました。
自動化したいコマンドやスクリプトの整理
まず、ターミナルで単体実行したときに確実に成功するコマンドだけを自動化対象にします。当たり前に聞こえますが、手動でたまに失敗するスクリプトをlaunchdに突っ込むと、原因がlaunchd側なのかスクリプト側なのか切り分けに苦労します。
私はシェル関数でワンライナー化し、bash -lc "スクリプトパス"の形で呼び出す派です。
plist配置ディレクトリの使い分け
plistの置き場所は用途ごとに3つあります。迷ったら~/Library/LaunchAgentsを選べばOKです。ログインユーザーの権限で動く、一番扱いやすい領域です。
| 配置場所 | 実行タイミング | 用途 |
|---|---|---|
| ~/Library/LaunchAgents | ユーザーログイン後 | 個人の自動化(推奨) |
| /Library/LaunchAgents | 全ユーザーのログイン後 | 複数ユーザー共有のMac |
| /Library/LaunchDaemons | OS起動直後 | root権限が必要な常駐 |
ログ出力先の決め方
launchdジョブはバックグラウンドで走るので、標準出力と標準エラーをファイルに落としておかないと、失敗した瞬間に証拠隠滅されます。私はプロジェクトごとにlaunchd_stderr.logを決め打ちで作り、エラーはそこに集約しています。
本体のアプリケーションログはスクリプト側でちゃんと別ファイルに出す設計が鉄板です。
launchdのplistを作る5ステップ手順
ここからが本番です。launchdのplistはXMLで書きますが、構造さえ覚えれば暗号ではありません。5つのステップに分けて進めれば、最短10分で最初のジョブが動き始めます。
サンプルとして「毎朝9時に~/bin/backup.shを叩く」ケースを前提にコードを置いておきます。
ステップ1: plistファイルの雛形を書く
ラベルはドメイン逆順で一意にします。私はcom.ユーザー名.スクリプト名で統一していて、launchctlの一覧で自分のジョブが一発で絞り込めて便利です。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.tomohiro.backup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-lc</string>
<string>/Users/tomohiro/bin/backup.sh</string>
</array>
<key>StandardErrorPath</key>
<string>/Users/tomohiro/bin/backup_stderr.log</string>
</dict>
</plist>ステップ2: StartCalendarIntervalで実行時刻を指定
時刻指定はStartCalendarIntervalを使います。毎朝9時ならHourとMinuteを指定するだけ。週次ならWeekday(0=日曜)、月次ならDayを足します。
<key>StartCalendarInterval</key>
<dict>
<key>Hour</key>
<integer>9</integer>
<key>Minute</key>
<integer>0</integer>
</dict>StartIntervalで秒数指定の定期実行を設定する
「毎朝9時」ではなく「10分おきに実行」のようなインターバル指定をしたい場合は、StartIntervalを使います。値は秒数で指定します。
<!-- 600秒 = 10分おきに実行 -->
<key>StartInterval</key>
<integer>600</integer>StartCalendarIntervalは「毎日○時○分」のような固定時刻向き、StartIntervalは「N秒ごと」のインターバル向きです。用途に応じて使い分けてください。
ステップ3: launchctlでロードして有効化
plistを~/Library/LaunchAgents/に置いたら、launchctlで読み込ませます。個人利用ではload/unloadで十分です。
# 有効化
launchctl load ~/Library/LaunchAgents/com.tomohiro.backup.plist
# 確認
launchctl list | grep tomohiro
# 無効化(plistを書き換えたら一度unloadしてからloadし直す)
launchctl unload ~/Library/LaunchAgents/com.tomohiro.backup.plist
# 新しいAPI(macOS 10.10以降): bootstrap/bootout も使える
# launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.tomohiro.backup.plist
# launchctl bootout gui/$(id -u)/com.tomohiro.backupステップ4: 手動実行で動作確認
スケジュール時刻まで待たずに、launchctl startで手動キックできます。ここで成功すれば、あとはlaunchdが指定時刻に勝手に動かしてくれます。失敗したらlaunchd_stderr.logを見に行く流れです。
launchctl start com.tomohiro.backup
tail -f /Users/tomohiro/bin/backup_stderr.logステップ5: plist構文チェックで事故を防ぐ
XMLのタイプミスはlaunchdがそっけなく無視するだけなので、ロード前にplutil -lintを通す癖をつけましょう。「OK」と返ってきたら合格、赤字が出たら行番号と一緒に教えてくれます。
私は過去に<integer>を<string>にして丸1日気づかなかった人間なので、声を大にして推奨しておきます。
launchd設定でハマった3つの落とし穴と対処法
ここからは私が実際に踏み抜いた地雷集です。launchdの公式ドキュメントだけ読んで設定すると、高確率で同じ穴に落ちます。先回りして潰しておけば、夜中の2時にMacBookの前でうなだれる時間を節約できます。
PATH環境変数が効かずスクリプトが動かない
launchd配下のプロセスは、あなたの.zshrcや.bash_profileを読みません。ターミナルで動くのにlaunchdから呼ぶと「command not found」になる原因は、この権限制約です。
対策は2つあります。スクリプト内で絶対パスを使うか、plistのEnvironmentVariablesキーでPATHを明示する方法です。私は後者でPATHを1行だけ追記する派です。
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin</string>
</dict>権限エラーでログが書き込めない
StandardErrorPathに指定したディレクトリが存在しないと、launchdは黙ってジョブを失敗させます。ログが出ないのでエラーに気づけない、という地獄のコンボが発生しがちです。plistを書いたら、touchで空のログファイルを先に作っておくと安全です。
plist編集後のリロードを忘れる
plistを直しただけでは新しい設定は反映されません。unloadしてからloadする二度手間が必須です。私は小さなシェル関数lreloadを作って、引数にplistパスを渡すだけで再読み込みできるようにしています。自動化の自動化、大事です。
私がlaunchdで自動化している実例と効果
理屈だけ並べてもピンと来ないので、私が実際にlaunchdへ投げているジョブを紹介します。どれも最初は手動で月に数回やっていたもので、面倒すぎて心が折れかけたタスクばかりです。
結果として、朝イチで結果だけSlackに届くので、私はコーヒーを飲むだけで済んでいます。
毎朝のブログ記事エクスポート
WordPressの全記事をREST API経由でHTMLに吐き出すPythonスクリプトを、毎朝6時に走らせています。手動で月1回やっていた頃は「あの記事どこ書いたっけ?」と過去を探す時間が無駄でしたが、ローカルに最新コピーがあるとgrep一発で見つかります。
StartCalendarIntervalでHour=6 Minute=0を指定するだけです。
週次の依存パッケージチェック
日曜の深夜4時45分に、全Pythonプロジェクトのpip-auditと依存パッケージの整合性チェックを走らせています。脆弱性が見つかればSlackに通知が飛ぶので、月曜の朝にコーヒー片手に対応方針を決められます。Weekdayを0に指定して、後ろの時間帯を週次ジョブで埋めるのがコツです。
月次のHomebrewアップデートとキャッシュ整理
毎月1日の早朝に、Homebrewの自動アップデートとキャッシュのクリーンアップを走らせています。手動でやっていた頃は「先月アップデートしたっけ?」と不安になることが多かったのですが、自動化してからは古いキャッシュが溜まって数GBのディスクを圧迫する事態もなくなりました。
StartCalendarIntervalでDay=1 Hour=5 Minute=30を指定しています。日次・週次・月次と3段階でジョブを仕込むと、手作業はほぼゼロになります。
ちなみに、2026年2月にはlaunchd-uiというGUI管理ツールも登場しました。plistの手書きに抵抗がある方は試してみる価値があります。
Mac launchdで自動化を始める3つのポイントまとめ
長くなったので、launchd自動化で本当に効く3つのポイントに絞って振り返ります。cronとの比較、plist作成の5ステップ、私の実運用と、話は広がりましたが、やることはシンプルです。
- launchd一本に絞る:cronと併用せず、macOSが面倒を見てくれる仕組みに寄せる。結果として事故が激減します。
- plistは小さく作って育てる:最初から完璧を狙わず、まずは1ジョブだけ動かして、成功体験を積んでから増やす。
- ログとplutilで事故を防ぐ:
StandardErrorPathとplutil -lintはセットで習慣化。夜中の原因不明エラーと無縁になります。
自動化は「作る時間」と「節約できる時間」のトレードオフです。launchdはplist1つを書くだけでロックオンできるので、投資回収がとにかく早い。私は今この瞬間も、launchdに仕込んだジョブが裏で10本以上走っていて、自分は記事を書くことに集中できています。
Macを「ただの道具」から「勝手に働く相棒」に変えたい人は、まず1ジョブだけでも登録してみてください。面倒なコマンドがMacの裏側で静かに片付いていく感覚、一度味わうと後戻りできません。