
macOS launchctl の基本 - launchd でジョブを動かす(plist と新旧コマンド)
「Mac で定期実行したい」「常駐プロセスを自動起動したい」——cron ではなく launchd / launchctl の出番です。ただ launchctl は新旧2系統のコマンドがあって混乱しがち。この記事では、plist の書き方と新構文(bootstrap 系)を中心に、基本を整理します(man・Apple 公式を一次ソースに確認)。
launchd / launchctl とは
- launchd: macOS の統合サービス管理(Tiger / 10.4〜、PID 1)。従来の init・cron・xinetd 等を置き換える存在
- launchctl: その launchd を操作する CLI
- 基本はオンデマンド起動。条件(ログイン・時刻・間隔など)に応じてジョブを起動します
LaunchAgents と LaunchDaemons
まず「Agent か Daemon か」を決めます。これで置き場所と権限が変わります。
| 種別 | 置き場所 | 起動 | 権限 |
|---|---|---|---|
| ユーザー Agent | ~/Library/LaunchAgents | ログイン時 | ログインユーザー(GUI可) |
| 全ユーザー Agent | /Library/LaunchAgents | 各ユーザーのログイン時 | ログインユーザー |
| Daemon | /Library/LaunchDaemons | ブート時(ログイン不要) | root(UserName で変更可) |
| OS 管理 | /System/Library/... | (触らない) | SIP で保護 |
- GUI を使う・ユーザー権限でよい・自分のログイン中だけ → Agent
- ログイン前から・root で・常時 → Daemon
plist の基本
ジョブは plist(XML)で定義します。主なキーは次のとおり。
| キー | 説明 |
|---|---|
Label(必須) | 一意な識別子。逆ドメイン記法(com.example.foo) |
ProgramArguments(必須) | 実行ファイル+引数の配列(第1要素が実行パス) |
RunAtLoad | 読み込み時に即実行 |
StartInterval | N秒ごとに実行 |
StartCalendarInterval | cron 的な時刻指定 |
KeepAlive | 常駐・自動再起動 |
StandardOutPath / StandardErrorPath | ログ出力先 |
EnvironmentVariables | 環境変数 |
最小例(ユーザー Agent):
<?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.myjob</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/myscript.sh</string>
</array>
<key>RunAtLoad</key>
<true/>
<key>StandardOutPath</key>
<string>/tmp/myjob.out</string>
<key>StandardErrorPath</key>
<string>/tmp/myjob.err</string>
</dict>
</plist>定期実行・常駐の指定
<key>StartInterval</key>
<integer>300</integer><key>StartCalendarInterval</key>
<dict>
<key>Hour</key><integer>3</integer>
<key>Minute</key><integer>55</integer>
</dict><key>KeepAlive</key>
<dict>
<key>SuccessfulExit</key><false/>
</dict>StartCalendarInterval は Minute / Hour / Day / Weekday / Month を指定でき、省略したキーはワイルドカード。スリープ中に来た時刻は、次回起動時に1回だけ実行されます(cron 同様、溜まって連発はしない)。
新コマンド(bootstrap 系)と旧コマンド
macOS 10.10 以降はドメイン指定の新構文が推奨です(旧 load/unload も動きますが非推奨)。
- ドメイン: Agent は
gui/$(id -u)、Daemon はsystem(sudo 必要)
| 操作 | 新(推奨) | 旧(legacy) |
|---|---|---|
| 読み込み | launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/foo.plist | launchctl load -w foo.plist |
| 解除 | launchctl bootout gui/$(id -u)/com.example.foo | launchctl unload -w foo.plist |
| 即時起動/再起動 | launchctl kickstart -k gui/$(id -u)/com.example.foo | launchctl start com.example.foo |
| 有効化/無効化 | launchctl enable|disable gui/$(id -u)/com.example.foo | load -w / unload -w 相当 |
| 状態確認 | launchctl print gui/$(id -u)/com.example.foo | launchctl list | grep foo |
| 一覧 | launchctl list | launchctl list |
Daemon の場合は sudo + system ドメイン:
sudo launchctl bootstrap system /Library/LaunchDaemons/com.example.foo.plist
sudo launchctl bootout system/com.example.foo
sudo launchctl print system/com.example.foo実用フロー
# 1. plist を検証(構文チェック)
plutil -lint ~/Library/LaunchAgents/com.example.myjob.plist
# 2. パーミッション(Agentは644・自分の所有でOK)
chmod 644 ~/Library/LaunchAgents/com.example.myjob.plist
# 3. 読み込み
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.myjob.plist
# 4. 確認
launchctl print gui/$(id -u)/com.example.myjob
launchctl list | grep myjobNOTE
Daemon(/Library/LaunchDaemons)は所有者 root:wheel・パーミッション 644が必要です(グループ/他者に書き込み権があると拒否されます)。sudo chown root:wheel ... && sudo chmod 644 ... を忘れずに。
plist を変更したら「bootout → bootstrap」
launchctl bootout gui/$(id -u)/com.example.myjob
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.example.myjob.plistWARNING
plist を編集しただけ・bootstrap し直しただけでは反映されません。 一度 bootout してから bootstrap し直すのが鉄則です。また disable 状態だと bootstrap しても起動しないので、その場合は先に enable します。
よくある落とし穴
- 絶対パス必須: launchd の
PATHは最小限。スクリプト内のコマンドもフルパスで書く(WorkingDirectoryも絶対パス) - ログ出力先を指定しないと消える:
StandardOutPath/StandardErrorPathを必ず設定。デバッグの命綱 - Daemon にユーザー環境は無い:
HOMEや dotfiles、PATHは引き継がれない。必要ならEnvironmentVariablesで明示 Labelの重複禁止: 重複するとロード失敗- すぐ終わるプロセスは再起動が抑制される: 既定の
ThrottleInterval(約10秒)より短時間で終了し続けると、launchd がクラッシュ扱いにして再起動を保留 -
SIP 保護領域(
/System/Library/...)は触らない launchctl printの出力はスクリプトでパースしない(Apple がリリースごとに変わりうると明言)
まとめ
- まず Agent(ログイン時・ユーザー)か Daemon(起動時・root)かを決め、対応する場所に plist を置く
- plist は
Label+ProgramArgumentsが基本。定期実行はStartCalendarInterval/StartInterval、常駐はKeepAlive - コマンドは新構文(
bootstrap/bootout/kickstart/print)を使う(旧load/unloadは非推奨だが動く) - 流れは 作成 →
plutil -lint→ 配置 →bootstrap→print/log。変更時はbootout→bootstrap - ハマりどころは 絶対パス・ログ出力先・最小PATH・10秒スロットル
cron に慣れていると最初は戸惑いますが、plist + bootstrap/bootout の型さえ掴めば、Mac の定期実行・常駐は launchd で完結します。ちなみに筆者はこの検証を tmux のペインから叩いていました(tmux のペイン位置の指し方)。シェルスクリプト側の安全策はset -euo pipefailも合わせてどうぞ。


