Launchdを使ってpythonコードを自動実行する〜plistファイルとload設定(linuxコマンドメイン)〜

launchd画像 Python
この記事は約12分で読めます。
Advertisements

読んで欲しい人

  • pythonで自作コード作成したが、自動実行の方法がわからない人
  • 少しだけLinuxおよびxml系も書ける人
  • cronからlaunchdに移行しようとしている人(正直WinユーザやLinux環境はcronでいい気もします)
  • Launchdの勉強をしたい人

シリーズ概要

  1. Launchd、plistファイルとload設定(linuxコマンドメイン)
  2. Launchd、ユーザエージェントで日に1回コードを自動実行させる
  3. Launchd、デーモンを利用してみる
  4. Launchd、基本的な設定で利用する情報郡

plistファイルの作成

step1 ファイル作成コマンドtouch

まずは、ファイル自体を作りましょう。基本demonでもuser Agentでも作成するファイルは全て.plistです。下記参考にあるように、ファイルを作成するフォルダによって、それぞれ持つ意味がことなってきます。

touch ~/Library/LaunchAgents/xxx.plist

touchというのが、ファイルを作成するコマンドです。最後のxxx部分は自分の好きなファイル名を入れてください。このxxx部分が後から紹介するLabelという概念になります。ファイル名=Labelと覚えておいてください。

参考)ファイルの作成場所別の用途

launchdの設定には大きく分けて2つの設定パターンがあります。

  • エージェントはユーザーがログイン中に実行できるプログラム。
  • デーモンはシステム共通で、誰もログインしていなくても実行できるプログラム。

エージェントやデーモンという言葉は耳慣れないかもしれませんが、そこまでわかりにく概念でもないので、是非理解してみてください。

また、実際のそれぞれエージェントにもユーザ管理か管理者管理(いわゆるrootユーザ権限)かの違いによって、ディレクトリが異なります。当然デーモンも異なります。

場所説明
~/Library/LaunchAgents各ユーザが管理する各ユーザユーザごとに実行するエージェントの格納ディレクトリ
/Library/LaunchAgents管理者が管理するする各ユーザごとに行するのエージェントの格納ディレクトリ
/Library/LaunchDaemons管理者が管理するシステムで実行するデーモンの格納ディレクトリ(ログイン状態に依らない)
/System/Library/LaunchAgentsOS が管理する各ユーザごとに実行するエージェントの格納ディレクトリ(基本的には触らない)
/System/Library/LaunchDaemonsOS が管理するシステムで実行するデーモンの格納ディレクトリ(基本的には触らない)

当たり前のように書いていますが、基本的理解としては、ファイルをおくディレクトリによって、同じ内容のplistファイルを設定したとしても、実行される挙動は異なるので、ご注意ください。

step2 ファイルの中身を書いていく

何はともあれ1つplistファイルを作成してみましょう。まず、作るには適当なテキストエディタで、以下のようなコードを作成します。

 <?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC -//Apple//DTDPLIST1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">
 <dict>
      <key>EnvironmentVariables</key>
        <dict/>
        <key>Label</key>
        <string>local.job-2</string>
        <key>ProgramArguments</key>
        <array>
               <string>python</string>
               <string>/Users/testscript/test.py</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardErrorPath</key>
        <string>/tmp/local.job-2.err</string>
        <key>StandardOutPath</key>
        <string>/tmp/local.job-2.out</string>
        <key>StartInterval</key>
        <integer>10</integer>
 </dict>
 </plist> 

初めてみるコードが羅列されてるかもしれません。ざっくり解説していきます。

そもそもplistって?

MACで利用されているxml系のファイルフォーマットです。1行目のコードにxml version=”1.0″ encoding=”UTF-8″と宣言されているように、xml1.0をベースにしています。

xmlの細かいことはよくわからなくても、とりあえずタグで囲まれた記載方法なんだなぁって覚えてもらえれば十分です。

まず初めにおまじない

最初の2〜3行は、正直おまじないです。意味は当然あるのですが、ノンプロがこの意味を理解するメリットはそこまでないので、おまじない(pythonでいう、実行するときにif __name__ == “__main__”:を書くみたいな)としてメモを残しておいて、必要なときにコピペすれば十分ですかね。

<?xml version="1.0" encoding="UTF-8"?>
 <!DOCTYPE plist PUBLIC -//Apple//DTDPLIST1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
 <plist version="1.0">

apple側が採用するバージョン等に変化がない限り問題なく動きます。

<dict> ~ </dict>までが全体メイン

 <dict>
      <key>EnvironmentVariables</key>
        <dict/>
        <key>Label</key>
        <string>local.job-2</string>
        <key>ProgramArguments</key>
        <array>
               <string>python</string>
               <string>/Users/testscript/test.py</string>
        </array>
        <key>RunAtLoad</key>
        <true/>
        <key>StandardErrorPath</key>
        <string>/tmp/local.job-2.err</string>
        <key>StandardOutPath</key>
        <string>/tmp/local.job-2.out</string>
        <key>StartInterval</key>
        <integer>10</integer>
 </dict>

誰がみてもわかる、ただ現時点では内容はわからない、と思いますが、dictで囲まれた部分がメイン(html文書でいうbody部分)です。

bodyの構成イメージとしては、<key></key>(以下、keyタグ)で囲われた部分でリクエストする内容を、その後の<string></string>(以下、stringタグ)で囲われた部分で実際に実行する処理内容を記載していきます。

さて、1つずつみていきましょう。

Labelを定義する
<key>Label</key>
<string>local.job-2</string>

keyタグでLabelを囲っていますので、ここはLabelを定義しています。

Labelといいうのは、launchdでこのplistファイルを読んでねって、お願いしている部分になります。

なので、step1でtouchで作成したファイル名を入れることになります。その際に.plist部分は不要です。動かすファイルは.plistってわかってるので、ファイルフォーマットの指定は不要って感じです。

実行したいファイルを定義する
<key>ProgramArguments</key>
<array>               
    <string>python</string>               
    <string>/Users/testscript/test.py</string>
</array>

keyタグでProgramArgumentsを囲っていますので、ここはProgramArgumentsを定義しています。

ProgramArgumentsといいうのは、シンプルにいうとlaunchdでこのlinuxコマンドを実行してねって、お願いしている部分になります。

なので、今回はpythonファイルを実行したい(前提として、今回のpyファイルはpyenv/venv/virtualenvなど仮想環境にpip installを行っていない物を実行しています)ので、ターミナルでpythonを実行するときに入力するコマンドを各stringタグに入れていくイメージです。

では、実際にターミナルで/Users/testscriptディレクトリにある、test.pyファイルを実行する場合は、どのようにコマンドするでしょうか?以下になりますね。

python /Users/testscript/test.py

実際のstringタグで囲われた部分は、このスペースで区切られた部分がそれぞれ記載されているだけですね。

つまり、ここでlinuxコマンドを一つづつstringタグで囲えばOKということになります。

自動起動する指定

<key>RunAtLoad</key>
<true/>

keyタグでRunAtLoadを囲っていますので、ここはRunAtLoadを定義しています。

RunAtLoadといいうのは、サービスのロード時(OS起動時を含む)に自動的にサービスを起動するかどうか、という設定になります。

今回は<true/>としているので、自動起動をオンにしています。オフにしたい場合は、ここを<false/>とするだけです。

エラーが起きた時とかにログを保存する

<key>StandardErrorPath</key>
<string>/tmp/local.job-2.err</string>        
<key>StandardOutPath</key>        
<string>/tmp/local.job-2.out</string>

keyタグでStandardErrorPath/ StandardOutPathを囲っていますので、ここはStandardErrorPath/ StandardOutPathを定義しています。

StandardErrorPathというのは、エラーが起きたときにログをどこに保存するかというもので、ログを保存するためのファイルpathを指定しています。

StandardOutPathというのは、標準出力と言って、加工済みデータの出力先や、ログ、メッセージ等情報出力用ファイルがあるpathを指定しています。

ちなみに、この二つは概ね呪文のように、tmpフォルダの中に<plistファイル名>.errと<plistファイル名>.outで定義します。

実行条件を設定する

<key>StartInterval</key>
<integer>10</integer>

keyタグでStartIntervalを囲っていますので、ここはStartIntervalを定義しています。

StartIntervalって何かといえば、名前の通り、どんな間隔で実行するかっていうGAS trigerでいうところのイベントの時間設定みたいなものです。今回はStartIntervalをテスト用に設定していますが、多くは”StartCalenderInterval”という、GAS trigerの時間主導型のイベント設定と同義の物を利用することが多いでしょう。

step3 ファイルが実行されるように設定する

さて、動作させたいplistファイルはできました!これで勝手にやってくれるはず!。。。

というのは、よくノンプログラマーにある勘違いで、どれだけファイルを設定しても、それをPC側が実行するファイルだよって命令を受けてないと動作してくれません。

ファイルを作るのは個人の自由ですが、PC君は君からの命令を待っているのですw ということで、実行するやつだよって明示するコマンドを撃ちましょう。また、ターミナルに戻ってください。以下のコマンドです。

launchctl load ~/Library/LaunchAgents/<ファイル名>.plist  #汎用的にはこんな感じ
launchctl load ~/Library/LaunchAgents/local.job-2.plist #今回のファイルは実際はこうなる

これでやっと実行してくれます。お疲れ様でした!

まとめ

いやぁ想定以上に長くなりました。お疲れ様です。

plistは実はpython標準ライブラリの中にもある要素なんですよ、知ってましたか?plistlibっていうモジュールが存在しています、暇なときにのぞいてみてください。

plistlib --- Generate and parse Apple .plist files — Python 3.9.0 ドキュメント

なお、これを書いてる段階でノンプロ研メンバーに問い合わせたところ、Automatorでできるんじゃない?って回答がw

とりあえず、launchd記事はあと何個か書くのですが、実はこれをちゃんと理解するより、Automatorを勉強した方が楽かもですねw いつかAutomator系の記事も勉強して書いてみます

コメント

  1. […] […]