Macで定期実行を仕込むならlaunchd一択です。
launchd(ローンチディー)はmacOS標準のジョブ管理の仕組みで、plistファイルを1つ書いてlaunchctl bootstrapで登録すれば、コマンドを毎日・毎週・N秒ごとに自動実行できます。
手順は「plist作成 → 実行時刻の指定 → launchctlで登録 → kickstartで動作確認 → plutilで構文チェック」の5ステップです。
私はブログのバックアップ、依存パッケージの更新チェック、毎月のスクリプトなど、気づけば手作業で回すコマンドが20本以上ありました。
正直めんどくさいし、忘れると地味にダメージがでかい。
そこで本気で使い倒しているのがmacOS標準のlaunchdです。cronじゃなくてlaunchd。ここ、重要です。
読み終える頃には、あなたのMacも勝手に仕事をしてくれる「小さな秘書」に化けているはずです。
この記事でわかること
- Macでcronよりlaunchdを選ぶべき3つの理由
- plistを書いて定期実行を登録するまでの5ステップ手順
- PATHや権限ではまらないための実践的なトラブル対策
- 私が実際にlaunchdへ流し込んでいる自動化ジョブの具体例
- 週1回のMac掃除をlaunchdで回すスクリプトとplistの実例
PARTS LIST /目次
Macのlaunchdで自動化するメリットとcronとの違い
結論から言うと、Macで定期実行を仕込むならlaunchd一択です。
cronでも動きはしますが、近年のmacOSはセキュリティまわりの制約が強くなっていて、普通に書いたcronジョブがフルディスクアクセスで弾かれるケースがよくあります。
~/Library/LaunchAgents/ に置くlaunchctl bootstrap登録・有効化最新のmacOS SequoiaやSonomaでもこの制約は続いています。
私も昔「なんで動かないの?」と丸一日溶かしました。launchdはmacOS自身が面倒を見てくれる仕組みなので、最初から筋が良いです。
そもそもlaunchdとは?cronが非推奨な理由
launchdはmacOSの起動直後から動いている常駐プロセスで、各種デーモンやユーザーエージェントを束ねる親玉のような存在です。
PID 1で動いていて、OSの中心にいる、と言えば伝わるでしょうか。
cronは歴史の長いUNIX資産ですが、Appleは公式にlaunchdを推奨しています。
sleep復帰やログイン連動など、Macらしい挙動を拾えるのはlaunchdだけです。素直に長いものに巻かれるのが吉です。
特に効くのがスリープ時の挙動です。Apple公式のman page(man launchd.plist)によると、cronはMacがスリープしている間の実行をスキップしますが、launchdのStartCalendarIntervalは復帰した時点でジョブを実行します。スリープ中に複数回分の実行時刻を過ぎていた場合は、1回にまとめて実行されます。
ノートPCのMacは夜間にスリープしていることが多いので、この差はかなり大きいです。
man launchd.plist)のとおり。launchd自動化で得られる3つのベネフィット
私の体感で一番大きいのは「忘れなくなる」部分です。
毎日深夜にブログ記事をエクスポートする、日曜の明け方に依存パッケージを監査する、こうした地味だけど大事なタスクをMacに預けた結果、手作業がほぼゼロになりました。
節約した時間で記事を1本書けるし、なにより「やり忘れた罪悪感」が消えるのがメンタルに効きます。
launchd自動化の前に準備する3つのもの
launchdは強力な反面、ちょっとお作法にうるさいタイプです。いきなりplistを書き始めると、配置先で迷ったり、ログが出なくて詰んだりします。
最初に3つだけ整理しておくと、あとの工程がびっくりするほどスムーズになります。
私が初めて設定したときに「先に知りたかった…」と後悔したポイントだけ厳選しました。
自動化したいコマンドやスクリプトの整理
まず、ターミナルで単体実行したときに確実に成功するコマンドだけを自動化対象にします。
当たり前に聞こえますが、手動でたまに失敗するスクリプトをlaunchdに突っ込むと、原因がlaunchd側なのかスクリプト側なのか切り分けに苦労します。
私はシェル関数でワンライナー化し、bash -lc "スクリプトパス"の形で呼び出す派です。
plist配置ディレクトリの使い分け
plistの置き場所は用途ごとに3つあります。迷ったら~/Library/LaunchAgentsを選べばOKです。
ログインユーザーの権限で動く、一番扱いやすい領域です。
ログ出力先の決め方
launchdジョブはバックグラウンドで走るので、標準出力と標準エラーをファイルに落としておかないと、失敗した瞬間に証拠隠滅されます。
私はプロジェクトごとにlaunchd_stderr.logを決め打ちで作り、エラーはそこに集約しています。
本体のアプリケーションログはスクリプト側でちゃんと別ファイルに出す設計が鉄板です。
launchdの使い方:plist作成から登録までの5ステップ
ここからが本番です。launchdのplistはXMLで書きますが、構造さえ覚えれば暗号ではありません。
5つのステップに分けて進めれば、最短10分で最初のジョブが動き始めます。
サンプルとして「毎朝9時に~/bin/backup.shを叩く」ケースを前提にコードを置いておきます。
plistを書いてから動作を確認するまでの流れを図にすると、次のようになります。
~/Library/LaunchAgents/ に配置StartCalendarInterval / StartIntervalplutil -lint で構文チェック登録する前に通すlaunchctl bootstrapgui/UID に登録launchctl kickstart今すぐ手動実行launchctl print の last exit code は0か?launchctl bootout で解除 → plutil -lint からやり直すステップ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.username.backup</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-lc</string>
<string>/Users/username/bin/backup.sh</string>
</array>
<key>StandardErrorPath</key>
<string>/Users/username/bin/backup_stderr.log</string>
</dict>
</plist>
ステップ2: StartCalendarIntervalで実行時刻を指定
時刻指定はStartCalendarIntervalを使います。毎朝9時ならHourとMinuteを指定するだけ。
週次ならWeekday(0と7が日曜)、月次ならDayを足します。指定しなかった項目はcronの「*」と同じくワイルドカード扱いです。
<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 bootstrapで登録して有効化
plistを~/Library/LaunchAgents/に置いたら、launchctlで読み込ませます。
現在のmacOSで推奨されているのはbootstrap(登録)とbootout(解除)です。man launchctlでは、古いload/unloadは「LEGACY SUBCOMMANDS」に分類され、代替としてbootstrap/bootout/enable/disableが推奨されています。
ユーザー単位のジョブはgui/$(id -u)(ログイン中ユーザーのドメイン)を指定して登録します。loadも今のところ動きますが、これから書くなら新しい形に揃えておくのがおすすめです。
# 登録(有効化): gui/<UID> はログイン中ユーザーのドメイン
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.username.backup.plist
# 確認(読み込まれていれば詳細が表示される)
launchctl print gui/$(id -u)/com.username.backup
# 登録解除(plistを書き換えたら一度bootoutしてからbootstrapし直す)
launchctl bootout gui/$(id -u)/com.username.backup
# 参考: 旧来のコマンド(レガシー扱いだが現在も動作する)
# launchctl load ~/Library/LaunchAgents/com.username.backup.plist
# launchctl unload ~/Library/LaunchAgents/com.username.backup.plist
ステップ4: kickstartで手動実行して動作確認
スケジュール時刻まで待たずに、launchctl kickstartで今すぐ実行できます。実行後はlaunchctl printの「last exit code」が0になっているかを確認します。
ここで成功すれば、あとはlaunchdが指定時刻に勝手に動かしてくれます。失敗したらlaunchd_stderr.logを見に行く流れです。
# スケジュールを待たずに今すぐ実行
launchctl kickstart gui/$(id -u)/com.username.backup
# 直前の終了コードを確認(0なら成功)
launchctl print gui/$(id -u)/com.username.backup | grep "last exit code"
# エラーログを監視
tail -f /Users/username/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」になる原因は、シェルの設定で足したPATHが引き継がれないことです。特にHomebrew(/opt/homebrew/bin)のコマンドでよく起きます。
対策は2つあります。スクリプト内で絶対パスを使うか、plistのEnvironmentVariablesキーでPATHを明示する方法です。
私は後者でPATHを1行だけ追記する派です。
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
権限エラーでログが書き込めない
StandardErrorPathに指定した出力先に書き込み権限がないと、ログが残らず、失敗に気づけません。なお、手元のmacOSで試したところ、親ディレクトリに書き込めるなら、存在しないディレクトリはlaunchdが作成し、ジョブも正常に実行されました。
ログが出ないのでエラーに気づけない、という地獄のコンボが発生しがちです。plistを書いたら、touchで空のログファイルを先に作り、書き込めることを確かめておくと安全です。
plist編集後のリロードを忘れる
plistを直しただけでは新しい設定は反映されません。launchctl bootoutで一度解除してからlaunchctl bootstrapで登録し直す二度手間が必須です(旧コマンドならunload→load)。
私は小さなシェル関数lreloadを作って、引数にplistパスを渡すだけで再読み込みできるようにしています。自動化の自動化、大事です。
plistを手書きしたくないならGUIツールlaunchd-uiも選択肢
XMLの手書きがどうしても面倒な場合は、GUIで管理するのも手です。2026年2月にはlaunchd-uiというオープンソースのGUI管理ツールが公開されました。
~/Library/LaunchAgentsのジョブ一覧の表示・検索、開始/停止、plistの閲覧・編集、新規エージェントの作成ができます(システム側のジョブは閲覧のみ)。まずGUIで全体像をつかみ、細かい設定はplistを直接書く、という使い分けもありです。
私がlaunchdで自動化している実例と効果
理屈だけ並べてもピンと来ないので、私が実際にlaunchdへ投げているジョブを紹介します。
どれも最初は手動で月に数回やっていたもので、面倒すぎて心が折れかけたタスクばかりです。
結果として、異常があればSlackやDiscordに通知が届くので、私はコーヒーを飲むだけで済んでいます。
毎朝のブログ記事エクスポート
WordPressの全記事をREST API経由でHTMLに吐き出すPythonスクリプトを、毎日深夜2時55分に走らせています。
手動で月1回やっていた頃は「あの記事どこ書いたっけ?」と過去を探す時間が無駄でしたが、ローカルに最新コピーがあるとgrep一発で見つかります。
StartCalendarIntervalでHour=2 Minute=55を指定するだけです。
週次の依存パッケージチェック
日曜の深夜4時45分に、全Pythonプロジェクトのpip-auditと依存パッケージの整合性チェックを走らせています。
脆弱性が見つかればSlackに通知が飛ぶので、月曜の朝にコーヒー片手に対応方針を決められます。
Weekdayを0に指定して、後ろの時間帯を週次ジョブで埋めるのがコツです。
週次のHomebrewアップデートとキャッシュ整理
毎週日曜の早朝4時に、Homebrewの自動アップデートとキャッシュのクリーンアップ(brew cleanup)を走らせています。
手動でやっていた頃は「先月アップデートしたっけ?」と不安になることが多かったのですが、自動化してからは古いキャッシュが溜まって数GBのディスクを圧迫する事態もなくなりました。
StartCalendarIntervalでWeekday=0 Hour=4 Minute=0を指定しています。月次のジョブ(毎月1日にブログの収益化候補を洗い出す処理など)はDay=1を指定して別のplistで回しています。
日次・週次・月次と3段階でジョブを仕込むと、手作業はほぼゼロになります。
実例: 週次のMacクリーンアップをlaunchdで回す
ここまでの手順を、そのまま使える形に落とし込んだ例をもう1つ置いておきます。Homebrew以外のキャッシュ(Xcode・Docker)も、週1回まとめて片付ける構成です。
開発用のMacは、HomebrewやXcode、Dockerのキャッシュで空き容量がじわじわ減っていきます。手で掃除するのは忘れがちなので、スクリプト1本とplist1つでlaunchdに任せてしまいます。
掃除スクリプトに入れる処理
対象は、消してもあとで再生成されるものか、もう要らなくなったものだけに絞ります。
- Xcode DerivedData:
~/Library/Developer/Xcode/DerivedDataはビルドのたびに膨らむキャッシュです。あわせてxcrun simctl delete unavailableで、今のXcodeが対応していない古いシミュレータを消します - Docker:
docker system prune -fとdocker builder prune -fで、停止中のコンテナやタグのないイメージ、ビルドキャッシュを消します。-fを付け忘れると確認プロンプトで止まり、launchdからの実行が終わりません - ~/Library/Caches:全部は消さず、
du -shでサイズの大きい上位20件をログに出すだけにします。アプリのキャッシュを消すと、再構築で逆にCPUを使うためです
~/Downloads も自動では消さないほうが安全です。find ~/Downloads -type f -mtime +90 -print で90日以上前のファイルを一覧に出すだけにして、消すかどうかは自分で決めます。
ゴミ箱(~/.Trash)はmacOSの保護対象で、フルディスクアクセスを許可していないプロセスからは「Operation not permitted」で拒否されます。launchdの掃除対象には入れず、Finderから空にするほうが確実です。
mac_clean.shの例
上の処理を1本にまとめたスクリプトです。launchdから起動するとPATHが最小限しか通らないので(落とし穴の「PATH環境変数が効かずスクリプトが動かない」のとおり)、先頭でHomebrewとDockerのパスを足しています。
#!/usr/bin/env bash
set -u
# launchdからはPATHが最小限なので、HomebrewとDockerのパスを足す
export PATH="/opt/homebrew/bin:/usr/local/bin:$PATH"
echo "[1/3] Xcode DerivedData"
rm -rf ~/Library/Developer/Xcode/DerivedData/*
xcrun simctl delete unavailable 2>/dev/null || true
echo "[2/3] Docker prune"
if command -v docker >/dev/null 2>&1; then
docker system prune -f
docker builder prune -f
fi
echo "[3/3] Cache visualization"
du -sh ~/Library/Caches/* 2>/dev/null | sort -h | tail -20
echo "mac_clean done: $(date)"
set -e にしていないのは、途中の1つが失敗しても残りの掃除を続けたいからです。dockerコマンドの有無を確かめているので、Dockerを入れていないMacでもそのまま使い回せます。
保存したら chmod +x mac_clean.sh で実行権限を付けておきます。plistの ProgramArguments にスクリプトを直接書くので、実行権限がないと起動できません。
毎週日曜4:30に動かすplist
plistは、ステップ1の雛形の Label・スクリプトのパス・StartCalendarInterval を書き換えるだけです。Weekday の0は日曜を表します。
<?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.example.mac-clean</string>
<key>ProgramArguments</key>
<array>
<string>/Users/you/scripts/mac_clean.sh</string>
</array>
<key>StartCalendarInterval</key>
<dict>
<key>Weekday</key><integer>0</integer>
<key>Hour</key><integer>4</integer>
<key>Minute</key><integer>30</integer>
</dict>
<key>StandardErrorPath</key>
<string>/tmp/mac_clean.err.log</string>
</dict>
</plist>
パスはフルパスで書きます。plistの中では ~ や $HOME は展開されません。登録と動作確認は、ステップ3・4と同じく launchctl bootstrap と launchctl kickstart で行います。
ほかのジョブと実行時刻をずらす
ジョブが増えてくると、つい同じ時刻にそろえたくなります。ただ、同時に起動するとCPUとディスクI/Oの負荷が重なり、スリープから復帰した直後のMacが重くなります。
上の実例では、日曜4:00にHomebrewの更新、4:45に依存パッケージのチェックを置いています。掃除はその間の4:30に入れると、起動が重なりません。
依存関係があるものだけ順番を意識して、あとは15〜30分ずつずらしておけば十分です。
無人で回すときの3つの注意点
掃除スクリプトはファイルを消すのが仕事なので、失敗したときの被害も大きくなりがちです。登録する前に、次の3つを確かめておきます。
- rm -rfは先に確かめる:スクリプトに入れる前に
lsで対象を確認し、頭にechoを付けたドライランで、消える対象を眺めてから本番にします - 対話が必要なコマンドは外す:
brew upgradeは一部のパッケージでsudoのパスワードを求め、masはApple IDの認証を求めることがあります。launchdからは応答できずに止まるので、手動実行用の別スクリプトに分けます - ログ自体も掃除する:
StandardErrorPathに指定したログは自動でローテーションされません。長く回すと少しずつ膨らむので、ときどき空にします
Mac launchdで自動化を始める3つのポイントまとめ
長くなったので、launchd自動化で本当に効く3つのポイントに絞って振り返ります。
cronとの比較、plist作成の5ステップ、私の実運用と、話は広がりましたが、やることはシンプルです。
- launchd一本に絞る:cronと併用せず、macOSが面倒を見てくれる仕組みに寄せる。結果として事故が激減します。
- plistは小さく作って育てる:最初から完璧を狙わず、まずは1ジョブだけ動かして、成功体験を積んでから増やす。
- ログとplutilで事故を防ぐ:
StandardErrorPathとplutil -lintはセットで習慣化。夜中の原因不明エラーと無縁になります。
自動化は「作る時間」と「節約できる時間」のトレードオフです。launchdはplist1つを書くだけでロックオンできるので、投資回収がとにかく早い。
私は今この瞬間も、launchdに仕込んだジョブが40本以上登録されていて、自分は記事を書くことに集中できています。
Macを「ただの道具」から「勝手に働く相棒」に変えたい人は、まず1ジョブだけでも登録してみてください。
面倒なコマンドがMacの裏側で静かに片付いていく感覚、一度味わうと後戻りできません。
| REV. | DATE | 内容 |
|---|---|---|
| 3 | 2026-10-19 | 週次のMacクリーンアップをlaunchdで回す実例を追加 |
| 2 | 2026-10-16 | 図解を描き直し(図面のデザインにそろえた) |
| 1 | 2026-04-11 | 初版 |



