logo hsb.horse
← ブログ一覧に戻る

ブログ

macOS Launchdで定期実行スクリプトを動かす

macOS LaunchdでCronのように毎分実行するスクリプトを構築する方法。環境変数の問題を回避し、ログ管理も含めたセットアップ手順。

公開日: 更新日:

翻訳

macOS Launchdを使って、Cronのように定期実行するスクリプトを構築する。 環境変数の整備が面倒なので、plist側の設定は最小にする方式をとる。

構成は以下の通り。

plist → wrapper shell → main shell

ラッパースクリプト

環境変数を設定し、実際のスクリプトを実行してログにリダイレクトする。

#!/bin/bash
# XDG環境変数の設定
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
# ログディレクトリの作成
LOG_DIR="$XDG_STATE_HOME/cron-like"
mkdir -p "$LOG_DIR"
# 実際のスクリプトを実行してログにリダイレクト
exec "$HOME/.local/bin/cron-like/every-minute.sh" \
>> "$LOG_DIR/stdout.log" 2>> "$LOG_DIR/stderr.log"

plist設定

毎分実行する設定。

<?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>localhost.cron.every-minute</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>/Users/username/.local/bin/cron-like/launchd.sh</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

セットアップスクリプト

一通りのセットアップを自動化する。

#!/bin/bash
set -e
# ディレクトリの作成
mkdir -p "$HOME/.local/bin/cron-like"
mkdir -p "$HOME/.local/state/cron-like"
mkdir -p "$HOME/Library/LaunchAgents"
# ラッパースクリプトの作成
cat > "$HOME/.local/bin/cron-like/launchd.sh" <<'EOF'
#!/bin/bash
# XDG環境変数の設定
export XDG_STATE_HOME="${XDG_STATE_HOME:-$HOME/.local/state}"
# ログディレクトリの作成
LOG_DIR="$XDG_STATE_HOME/cron-like"
mkdir -p "$LOG_DIR"
# 実際のスクリプトを実行してログにリダイレクト
exec "$HOME/.local/bin/cron-like/every-minute.sh" \
>> "$LOG_DIR/stdout.log" 2>> "$LOG_DIR/stderr.log"
EOF
# 実行スクリプトの作成
cat > "$HOME/.local/bin/cron-like/every-minute.sh" <<'EOF'
#!/bin/bash
# 実際の処理
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Running every-minute task"
# ここに実際の処理を記述
EOF
# 実行権限の付与
chmod +x "$HOME/.local/bin/cron-like/launchd.sh"
chmod +x "$HOME/.local/bin/cron-like/every-minute.sh"
# plistの作成
LABEL="localhost.cron.every-minute"
PLIST_PATH="$HOME/Library/LaunchAgents/${LABEL}.plist"
cat > "$PLIST_PATH" <<EOF
<?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>${LABEL}</string>
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>$HOME/.local/bin/cron-like/launchd.sh</string>
</array>
<key>StartInterval</key>
<integer>60</integer>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
EOF
# 構文チェック
plutil -lint "$PLIST_PATH"
# 既にロードされている場合はアンロード
launchctl list | grep -q "$LABEL" && launchctl unload "$PLIST_PATH" 2>/dev/null || true
# ロード
launchctl load "$PLIST_PATH"
echo "✓ Setup complete!"
echo " Scripts: $HOME/.local/bin/cron-like/"
echo " Logs: $HOME/.local/state/cron-like/"
echo " plist: $PLIST_PATH"
echo ""
echo "Commands:"
echo " tail -f $HOME/.local/state/cron-like/stdout.log"
echo " launchctl start $LABEL"
echo " launchctl unload $PLIST_PATH"