AppleScript でファイルの参照

さて。AppleScript でファイルを開くいろいろな方法をみてきました。

Finder や System Events などのアプリケーションや StandardAdditions.osax のファイル選択ダイアログを使えば、処理したいファイルの指定はそれほど難しいものではありません。

しかし、スクリプトの中ではもっと直接的にファイルの指定を行いたいときが多々あります。

ということで、今回は AppleScript で利用するファイルの参照、パスの話です。


HFS パス

OS X 以前の話です。その頃 AppleScript はコロン(:)区切りのパスを使っていました。

ユーザー cherry の書類フォルダの中にある style.css というファイルを表すと、次のようになります。

-- Macintosh HD は起動ディスク
"Macintosh HD:Users:cherry:Documents:style.css"

このコロン区切りのパスを「HFS パス」と呼びます。HFS は OS X のファイルシステムですね。このパスはルート(この例だと起動ディスク)から書き始めます。だから、他の人の環境だと異なっているかもしれませんし、外部ディスクになるとルートも変わります。

現在の AppleScript だとルートの省略ができるようで、以下のような書き方でもありです

-- 起動ディスク以下の Applications フォルダ
"Applications:"

HFS パスではコロンまでが一つのディレクトリです。

"Macintosh HD:" -- ルートディレクトリ
"Macintosh HD:Users:cherry:Documents:" -- 書類フォルダ
"Macintosh HD:Users:cherry:Documents:style.css" -- style.css ファイル

最初のコロンまでが起動ディスク(もしくはルートディレクトリ)を表しています。最後にコロンがつくとディレクトリ(フォルダ)、そうでないならファイルとみなされます。

OS X 以前から利用されていたということもあり、AppleScript ではこの HFS パスを多用します。これは現在(OS X 10.11)でも変わっていません。

POSIX パス

POSIX パスは UNIX ベースの OS X ではなじみが深いものです。スラッシュ区切りのこのパスは、さまざまな場面で目にするものです。

先ほどのユーザー cherry の書類フォルダの中にある style.css を POSIX パスで表すと次のようになります。

"/Users/cherry/Documents/style.css"

簡潔ですね。この POSIX パスは特別なことをしなくても AppleScript では(文字列として)利用できます。

AppleScript というのはグルー言語です。いろんな機能を組み合わせて複雑なバッチ処理を行うことを目的としています。シェルスクリプトなんかと同じですね。

UNIX ベースの OS X なんだからバッチ処理はシェルスクリプトでいいじゃないか、と言えばその通りなのですが、AppleScript の優位性というのは各種アプリケーションを操作することができることです。

もともとはアプリケーションの自動化が主な目的だったのですが、現在では AppleScript を中心にシェル(を通しての外部プログラミング言語)、Objective-C、JavaScript...と、いろいろなプログラミング言語を結びつける役割も果たしています。

こういう特性のため AppleScript でも POSIX パスを利用することが多々あります。ファイルの選択は AppleScript で行って処理はシェルで行う...といった感じですね。

ファイル参照のためのクラス

POSIX パスも HFS パスもファイルやフォルダの場所を示すための記述方式にすぎません。ぶっちゃけただの文字列です。このままでは AppleScript ではファイルの参照として認識しません。

では AppleScript でファイルを参照するにはどうするのかというと、alias、もしくは file クラスを利用します。

ごくおおざっぱにいってしまえば、alias クラスは『既にあるファイルを指定するためのクラス』で file クラスは『存在していないファイルを指定するためのクラス』です。

既にファイルがある場合に使うのが alias クラスです。

alias "Macintosh HD:Users:cherry:Desktop:Work:main.html"

デスクトップの Work フォルダにある main.html というファイルを指定しています。このとき、この場所にファイルがないと実行時にエラーになります(古いバージョンの AppleScript ではコンパイル時にエラーになる)。

AppleScript は実行時に alias で指定されたファイルやフォルダを記憶します。記憶しておくことで、次回からはファイルやフォルダを追跡します。つまり、ファイルやフォルダを移動させたり、名前を変更しても大丈夫なんです。

次のスクリプトを試してみましょう。


初回実行時は変数 theFile が未定義なので必ずエラーになります。エラーが起きるとファイル選択を求められます。その後、現在の場所を表示し、適当な場所に移動させます。

もう一度、実行してみましょう。ファイルは元の場所にないにも関わらず、ファイル選択を求められませんし、ファイルは再度異なった場所に移動されます。

alias は指定されたファイルやフォルダの情報を記憶します。このため、ファイルの位置や名前が変更されても追跡することができます。そういう特殊なもののため、実行時に指定するファイルやフォルダが存在しないとエラーになります。

いまいちピンとこない...という方もいると思われます。もうちょっと直感的に理解できる仕組みが Finder にあります。それは、Finder で作るエイリアス(「ファイル」メニューの「エイリアスを作成」)です。

この Finder で作るエイリアスと AppleScript の alias クラスは同じ仕組みです。

では、file クラスをみてみましょう。

file クラスは alias とは異なり、常に一つの場所を参照します。指定された場所にファイルがなければファイルを作成し、あれば、そのファイルを使用します。alias のようにオリジナルのファイルを追跡しません。

例えば、以下のようにすればデスクトップに「new file.md」というファイルを作成します。

-- ファイルのパスを文字列から作る
set theFile to (path to desktop as text) & "new file.md"
-- パスを file クラスとして扱い新規ファイルを作成
set fh to open for access file theFile with write permission
close access fh

再度このスクリプトを実行すると、すでにある「new file.md」に上書きします(なにもデータを追加していませんが)。「new file.md」を他の場所に移動させ、再度実行すると同じ場所に同じ名前でファイルを作成します。

このように file クラスは常に指定された場所だけを参照します。

aliasfile は、それぞれ同じようにファイルを参照しますが、その意味するところは異なっています。目的に合わせて利用することが大事です。

パスの相互変換

現在では、POSIX パスでファイルを指定するのが、記述のしやすさや汎用性の点からいっても楽です。指定したいファイルやフォルダをそのまま Script Editor.app のドキュメントにドラッグ & ドロップして挿入もできますし。

しかし、AppleScript では POSIX パスをそのまま利用するということは稀です。もう少し厳密に書くと、AppleScript やアプリケーションで定義されている命令やクラスが POSIX パスを受け取るようになっていません。

パスの指定は POSIX パスが楽だけど、AppleScript の命令などで利用するにはどうすればいいのか?

そのためにパスの変換を行います。パスの変換方法を覚えれば、HFS パスと POSIX パスを相互に変換できます。

変換には as 演算子を使います。as 演算子は型の変換を行う演算子です。

set num to 29.87
class of num
--> real
-- 整数値に変換
set num to num as integer
class of num
--> integer
-- 文字列に変換
set num to num as text
class of num
--> text

変換できない値を変換しようとするとエラーになりますが、as による型変換は頻繁に利用するので覚えておきましょう。

AppleScript ではファイル参照に利用するためのクラスが 3 つあります。aliasfilePOSIX file です。aliasfile クラスは後で説明します。POSIX パスを変換するために利用するのが POSIX file です。

POSIX file というのはちょっと特殊なクラスで POSIX パスを HFS パスに変換するためだけに利用します。基本的には後で説明する file クラスと同等のものです。

-- Applications フォルダ内の Mail.app
"/Applications/Mail.app" as POSIX file
--> file "Macintosh HD:Applications:Mail.app"

変換すると file クラスのパスが返ってきます。この変換は頻繁に利用するので、わざわざ as で変換しなくてもいいようになっています。

-- 次の書き方でも可
POSIX file "/Applications/Mail.app"
--> file "Macintosh HD:Applications:Mail.app"

ちなみに POSIX パスでは ~ が現在ログインしているユーザーのホームを表していますが、AppleScript では正しく解釈されません。POSIX パスを利用するときは相対パスではなく、絶対パスを使ってください。

fileas 演算子で alias に変換することもできます。

set theFolder to POSIX file "/usr/local/bin/"
tell application id "com.apple.finder" to open (theFolder as alias)

file クラスも alias クラスも as 演算子で HFS パス文字列に変換できます。

set theFolder to POSIX file "/usr/local/bin/"
theFolder as text
--> "Macintosh HD:usr:local:bin:"

次に HFS パスを POSIX パスに変換するにはどうするのか?

alias クラスと file クラスはそれぞれ、POSIX path という属性を持っているので、これを利用します。

-- POSIX パスを file に変換
set theFile to POSIX file "/Applications/Utilities/X11.app"
--> file "Macintosh HD:Applications:Utilities:X11.app"
-- file を POSIX パスに変換
POSIX path of theFile
--> "/Applications/Utilities/X11.app"

POSIX file はクラスだけど、POSIX path は属性です。このへんを勘違いしないようにしましょう。

いろいろなパスを取得する

スクリプトからファイルを参照するための記述方法をみてきました。今度はスクリプトの中からいろいろなパスを取得してみましょう。

StandardAddtions.osax に path to という命令があります。この命令は特定の場所を返してくれる便利な命令です。

例えば、実行しているスクリプトの場所を得るには次のようにします。

path to me

スクリプトが保存されているならファイルの場所が返ってきます。では、ログインしているユーザーのホームを取得してみます。

path to home folder

どんどんいきましょう。

-- 現在のユーザーの書類フォルダ
path to documents folder
-- 現在のユーザーのピクチャーフォルダ
path to pictures folder
-- 現在のユーザーのムービーフォルダ
path to movies folder
-- 現在のユーザーのデスクトップ
path to desktop
-- 現在のユーザーのダウンロードフォルダ
path to downloads folder

これらのパスを path to で取得するのはユーザーによってユーザー名や起動ディスク名などが異なるからですね。異なっていても同じようにスクリプトを動かすために path to を使います。

では、次のスクリプトを実行してみましょう。

path to library folder

今度はユーザーのライブラリフォルダの場所が返ってくるかと思いきや、違う場所が返ってきました。

OS X にはライブラリフォルダが複数箇所にあります。システムが利用するもの、ログインしている全ユーザーが利用するもの、そして、現在ログインしているユーザーが使用するもの。これらのうちどのライブラリフォルダかを指定していないため、予想と異なった場所が返ってきたのです。

path to 命令には from というオプションがあり、このオプションでどの場所かを指定します。

-- 現在のユーザーのライブラリフォルダ
path to library folder from user domain
-- 全ユーザーが利用するライブラリフォルダ
path to library folder from local domain
-- システムが利用するライブラリフォルダ
path to library folder from system domain

このように複数箇所にあるフォルダのパスを得るときは from による場所の指定を忘れないようにしましょう。

また、path to 命令には as オプションがあり、パスを alias で取得するか、文字列で取得するかを指定することができます。デフォルトでは alias でパスが返ってきます。

文字列で取得するのは、起点となるパスから特定のファイルを指定するためです。ユーザーの書類フォルダの中にある style.css ファイルを取得するなら次のようにします。

set documentsFolder to path to documents folder as text
set theFile to documentsFolder & "style.css"

これで各環境におけるユーザー名の違いや起動ディスク名の違いを考慮する必要がなくなります。

特定のフォルダのパスを文字列で取得し、目的のファイル名と文字列の結合を行い、filealias、もしくは POSIX パスに変換...と、これがファイル参照の一連の手続きですが、この操作は頻繁に行うのでいろいろと試して慣れておくといいでしょう。

ときどき目にする勘違いなのですが、これが AppleScript におけるファイル参照であって、Finder や System Events 等のファイルを扱うアプリケーションによるファイルの参照とは別のものです。これらのアプリケーションにおけるファイルの参照はファイルの参照ではなく、オブジェクトの参照だということを間違えないようにしましょう。

AppleScript でファイルを開いてみましょ

Finder でファイルをダブルクリックし、ファイルを開き、アプリケーションで処理をする。もしくは、Finder でファイルを選択し、それをアプリケーションアイコンにドラッグ & ドロップしてファイルを開く...。

たかだかファイルやフォルダを開くだけなのですが、日常的にやっている作業でもあります。それゆえに自動化の恩恵はそれなりにあるのではないでしょうか。

この作業を Applecrip で代替してみましょう。


例えば未対応なアプリケーションで

アプリケーションの起動と終了」でも書いたようにアプリケーションは AppleScript に対応しているものとそうでないものがあります。アプリケーションが AppleScript に対応していてもどのていど対応しているかが異なります。

しかし、ファイルを開くということだけならほとんどのアプリケーションで利用することができます。アーカイブユーティリティ.app は OS X に標準でついてくる圧縮されたファイルを解凍するアプリケーションですが、これは AppleScript に対応していません。しかし、基本的な命令をうけつけるアプリケーションでもあります。

では、AppleScript からファイルを開くとどうなるか?

-- デスクトップにある Archive.zip を指定
set theFile to (path to desktop folder as text) & "Archive.zip"
tell application "Archive Utility"
    -- アーカイブユーティリティで開く
    open file theFile
end tell

予想通り、圧縮されたファイルが解凍されます。このようなアプリケーションは(AppleScript で操作できているように見えますが)「AppleScript 未対応」です。本来の AppleScript に対応しているアプリケーションは「用語説明を持っている」アプリケーションです。分かりにくいのですがここでは未対応に分類します。

と、こういうことを踏まえて、AppleScript でどこまでできるのか?

ファイルを開く基本は Finder です

AppleScript に対応していなくてもファイルを開くだけであれば、Finder を通せばどんなファイルでも開けます(そのファイルを開けるアプリケーションがあれば)。

tell application id "com.apple.finder"
    -- ウィンドウがなければ終了
    if not (front Finder window exists) then return
    tell front Finder window
        -- 前面のウィンドウにある拡張子が mp4 のファイルを取得
        set fileList to document files whose name extension is "mp4"
    end tell
    -- ファイルを開く
    open fileList
end tell

これは、Finder でファイルをダブルクッリクする動作と同じで、デフォルトのアプリケーションでファイルを開きます。なにも変更していないなら、QuickTime Player が起動してファイルが表示されます。

また、Finder は開くアプリケーションを指定してファイルを開くことができます。これも AppleScript で制御できます。openusing オプションを利用します。

tell application id "com.apple.finder"
    -- ウィンドウがなければ終了
    if not (front Finder window exists) then return
    tell front Finder window
        -- 前面のウィンドウにある拡張子が mp4 のファイルを取得
        set fileList to document files whose name extension is "mp4"
    end tell
    -- ファイルを VLC.app を使って開く
    open fileList using (path to application "VLC")
end tell

この方法を使えば、テキストファイルなら CotEditor で、画像ファイルなら Photoshop でといったこともできますね。

open と Finder の open の違い

AppleScript を書き始めたころ理解しにくかったのが、同じ命令がいくつもあることでした。

Finder の open は Finder で定義されているもので using なんていう便利なオプションが追加されています。

もう一方の open は AppleScript で定義されているもので using なんてオプションはありません。でもね、見た目は一緒なんです。では、それらをどうやって見分けるのか?

これらの見分け方は命令を送っている対象が誰か?で分かります。

QuickTime Player は AppleScript に対応しており、open 命令を持っています。

tell application "QuickTime Player"
    open fileList
end tell

だけど、using というオプションはないのでただ、ファイルを開くだけです。

命令を送っている対象というのは tell 構文で指定されています。tell で指定されていないなら、それは AppleScript に最終的に送られます。

tell 構文というのは命令を送る対象を指定する構文なので、対象はアプリケーションでなくてもかまいません。

set theList to {1, 2, 4, 10.1, "文字", 0.1, "abc"}
tell theList
    length
    --> 7
    strings
    --> {"文字", "abc"}
    numbers
    --> {1, 2, 4, 10.1, 0.1}
    reverse
    --> {"abc", 0.1, "文字", 10.1, 4, 2, 1}
end tell

どのアプリケーションが持っている命令か?どこに命令を送っているか?というのは結構重要なことでこれを見極めていないとスクリプトがエラーになる、動かないといった原因になります。

再びの URL Scheme

以前にも URL Scheme のことを書いたことがありますが、あれは iPhone アプリのことでした。現在では OS X のアプリケーションでも AppleScript には対応していないけど、URL Scheme は設定されているというアプリケーションが増えてきました。それらを使ってファイルを開いてみましょう。

Apple 純正のものだと辞書.app や FaceTime.app、App Store.app、連絡先.app などがわりと有名ですね。

URL を AppleScript で開くには open location 命令を使います。この命令は URL をデフォルトのアプリケーションで開く命令です。http で始まる URL なら Safari が起動しますし、itms で始まるなら iTunes が起動します。

ちょっと余談なんですが、open location は StandardAdditions.osax という AppleScript 独自の機能拡張で定義されています。特別に変な使い方をしていない限り、StandardAdditions.osax は必ずインストールされています。

普段は意識せずに使っているので気がつきませんが、StandardAdditions.osax は AppleScript に足りない機能を追加するかなり重要なものです。AppleScript 自体に定義されている命令なんて activatecopycountgetlaunchrunset ぐらいです(いま delay の説明を見ていたら delay はビルトインコマンドだと書かれていた。なら、StandardAdditions.osax の用語説明にのせるなよ。っていうかいつのまに変わった?)。

たかだかこれだけの命令しかないので StandardAdditions.osax がないとなにもできなかったりします。なんでこのことを長々と書いているかというと、どこで定義されている命令かということが重要なのと、OS X 10.8 以降では use を使って StandardAddition.osax の命令を使うよって宣言が必要になるからです(まぁ、面倒)。

閑話休題。

URL Scheme は基本的にはそのまま開いてしまえば、アプリケーションが起動します。

-- 連絡先.app を起動
open location "addressbook://"
-- カレンダー.app を起動
open location "ical://"
-- 辞書.app を起動
open location "dict://"
-- FaceTime.app を起動
open location "facetime://"
-- App Store.app をセキュアに起動
open location "macappstores://"

しかし、URL Scheme には便利なオプションがあります。例えば、連絡先.app なら登録している人の ID を指定して編集を行う事ができます。

tell application id "com.apple.AddressBook"
    set theList to selection
    if theList is {} then return
    set personID to id of item 1 of theList
end tell
open location "addressbook://" & personID & "?edit"

ほとんど意味のないサンプルですが、URL Scheme を使うとこういうことが出来ます。が、問題なのは URL Scheme に渡すことができるオプションや値の説明がほとんどない、ということです。

マップ.app の URL Scheme は Map Links で説明がありますが、こういう説明はほとんどの場合ありません。なので自分で検索して使い方を見つけるしかないのが現状です。これが、URL Scheme の問題点。

Apple のアプリケーションでさえまともな説明がないのですから、その他のアプリケーションなんかほとんど使い方が分かりません。使えると便利そうなのだけど、使い勝手があまり考慮されていないという...。

自分の環境で使える URL Scheme を調べるには「自分のMacで使えるURLスキームをリスト表示するコマンド - Macの手書き説明書」を参考にしてみてください。

OS X 上での URL Scheme はアプリケーションで設定されているものを使うより、自分で好きな URL Scheme を作ってそれを使うというのが便利な気がします。

この方法を使えば、AppleScript でなんでもかんでも処理できてしまいますね。

open ハンドラを使ってみる

最後に話を戻して...open についてもう一度。

これまで書いてきたように open をそのまま使ってファイルをアプリケーションに渡す...というのは AppleScript ではどちらかというとあまり利用されていない使い方だと思います。

では、どういう使い方をするのかというと open ハンドラを使ってドロップレットを作る...というのがよく見かけるものです。

on open theseItems
    -- ドラッグ & ドロップされた項目を処理します
    repeat with thisItem in theseItems
        -- ここのファイル処理を記述します
    end repeat
end open

これを Script Editor.app でアプリケーションとして保存すると、ドラッグ & ドロップを受け入れるアプリケーション(ドロップレットと呼ばれたりします)になります。

そうなんです。スクリプトはアプリケーションとして保存することができるのです。

on run
    -- ここに起動したときの処理
    display dialog "run イベントが呼び出されました"
end run
on open theseItems
    -- ドラッグ & ドロップされた項目を処理します
    display dialog "open イベントが呼び出されました"
    repeat with thisItem in theseItems
        -- ここのファイル処理を記述します
    end repeat
end open
on reopen
    -- ここに reopen イベントを受け取ったときの処理
    display dialog "reopen イベントが呼び出されました"
end reopen
on quit
    -- ここに終了時の処理
    display dialog "quit イベントが呼び出されました"
    continue quit
end quit
on idle
    -- ここに一定時間ごとに行う処理
    display dialog "idle イベントの定期処理です"
    return 60
end idle

このスクリプトを「実行後、自動的に終了しない」アプリケーションとして保存します。起動させると最初に runidle が呼び出されます。idle はこの後、60 秒後に再度呼び出されます。

Dock にあるアイコンをクリックすると reopen が呼び出されます。Finder からなんらかの項目をドラッグ & ドロップすると open が呼び出されます。終了させると quit が呼び出されます。

完全にアプリケーションですね。で、このスクリプトをみてなにか思いつかないでしょうか?

そうです。先に書いた「基本的な命令を受けつけるアプリケーション」そのものです。実のところ、このサンプルを通しての方が「基本的な命令を受け付けるアプリケーション」っていうものが理解しやすいと思います。それらのアプリケーションはこういう中身をしているんだ、と(ごくごく乱暴にまとめてしまっていますが、動作原理を知る分にはいいでしょう...?)。

起動中のアプリケーションに run を送ったとき、なにをするかはアプリケーション次第、と書きました。run は基本的には起動時に一回処理されるだけなのですが、このサンプルをみるともう一回ダイアログが表示されますね。

open ハンドラがあれば外部からの AppleScript でファイルを開くことができます。reopen ハンドラがあれば、Dock のアプリケーションアイコンのクリックで動作します。で、どんな処理をするかはアプリケーション次第です。

こういう AppleScript アプリケーションを作って外部から操作したりするとより AppleScript の理解ははやまると思います。

では、実際に open ハンドラを使ったサンプルを作ってみます。

Finder でファイルをダブルクリックすると通常ならそのまま設定されているアプリケーションでファイルが開かれます。例えば、拡張子 md の Markdown 書類だと Xcode で開かれたりします(Xcode がインストールされていなるなら)。しかし、Atom で開きたい、もしくは CotEditor で開きたいなんて場合もあります。

Finder の「情報をみる」でファイルを開くアプリケーションを変更することはできますが、それも面倒だったりします。

特定のファイルだったら、全部 CotEditor で開いてしまえ、というものを作りましょう。が、なるべく手間をかけずにやってしまいたい。


これをアプリケーションとして保存します。拡張子 md、html、json、py のファイルをドラッグ & ドロップすることで CotEditor でファイルを開くことができます。これら以外のファイルだった場合は Finder に処理を任せてしまいます。拡張子を追加すれば、他のファイルにも対応できます。

これでいちいちデフォルトのアプリケーションを変更する必要もないですし、Finder のウィンドウのツールバーにでも置いておけば、どこからでも利用できます。また、デフォルトのアプリケーションで開きたいのなら Finder でダブルクリックすれば事足ります。

ファイルを開くってことだけで結構いろいろできるでしょう?

...と、これで話が終わればいいんですが、初心者向きのスクリプトばかりで面白みがないなぁという人(誰だ、それは)向けに。

以下は完全に蛇足です。


本当は最初に書いたスクリプトはこれなんですけどね。分かりにくいだろうし、説明も面倒なことになりそうだし...ということで採用を見送りました。

こっちの方がカスタマイズしやすいし、シンプルなんだけどね(自画自賛)。

あと、通常なら Finder でファイルをダブルクリックして開くとデフォルトのアプリケーションが起動します。

が、これを Finder のダブルクリックで AppleScript で作ったアプリケーションを起動させてしまえば、いいんじゃねと思ったのでそんなこともやってみました。例えば、JPEG ファイルをダブルクリック、AppleScript でリサイズ、Mail.app で開くみたいな感じです。

まぁ、作ってからドロップレットでいいじゃんと反省したのですが、単純に Finder のダブルクリックに反応させるにはどうしたらいいのだろう、と思ったのです。

作り方は Info.plist を編集するだけでできます。

まぁ、本当の蛇足ですね。これは。

AppleScript でアプリケーションの起動と終了

Pseudo TotalTerminal(偽の TotalTerminal)を書いていて思ったのですが、アプリケーションを起動させるってことだけでも
AppleScript ではいろいろ書き方があります。

ちょっと思いついただけでも activatelaunchopenreopenrun...と、さまざま。これらすべて挙動が異なっているのですが、どうも違いが分かりにくい。だからかどうかは分からないのですが、System Events を使ったプロセスの制御を行うスクリプトをよく見かけます。

どうもその手のスクリプトをみていると...修正したくなります。いやいやそんな面倒なことをしなくても...なんて。

たぶん日本語で読める AppleScript の説明が少ないのが一番の問題なのでしょうが。そういうことを踏まえてちょっと AppleScript の基本的なことをまとめてみます。

まずはアプリケーションの制御...起動と終了についてです。


アプリケーションの起動

activate はアプリケーションが起動していないなら起動させ、最前面に表示します。Finder でアプリケーションアイコンをダブルクリックしたときの挙動ですね。

アプリケーション起動の最も一般的な命令です。

-- アプリケーションを必要なら起動し、最前面に表示
tell application id "com.apple.Terminal" to activate
activate application id "com.apple.Terminal"

並べて書いていますが、どちらも効果は同じです(意味はちょっと違うけど、そこを深く追求しだすと分かりにくくなるので)。

activate は表示されているアプリケーションに対して使う命令です。つまり、バックグラウンドアプリケーションに対しては本来使いません(そもそもバックグラウンドアプリケーションは最前面に表示されない)。

launch もアプリケーションを起動させる命令ですが、すでにアプリケーションが起動しているならなにも効果はありません。activate のように最前面にしたりはしません。

また、アプリケーションが起動していないなら起動を行いますが、初期化処理は行いません。Terminal は通常なら、起動時にウィンドウを表示させますが、launch で起動させた場合はウィンドウ表示が行われません。

-- アプリケーションを起動させるが、初期化処理は行わない
tell application id "com.apple.Terminal" to launch
launch application id "com.apple.Terminal"

run 命令でもアプリケーションを起動させます。しかし、アプリケーションは非表示で起動されます。

-- アプリケーションを起動させるが、非表示
tell application id "com.apple.Terminal" to run
run application id "com.apple.Terminal"

すでにアプリケーションが起動している場合、run ではなにが実行されるかアプリケーションの設定により異なります。この辺りが activatelaunch と異なるところです。アプリケーション側が run 命令を受け取った時にこうしなさい、というプログラムが組まれているならそれを行います。

多くの場合はなにも起こりませんが、再度、初期化処理が行われる場合もあります。

open 命令は直接アプリケーションを起動する命令ではありません。

分かりやすくいうと「ファイルをアプリケーションアイコンにドラッグ & ドロップする動作」と同じです。結果としてアプリケーションを起動させてファイルを開かせることになります。

今までの命令と異なるのは引数として開かせる対象のファイルやフォルダが必要になることです。

-- 画像ファイル
set imageFile to (path to pictures folder as text) & "2016.jpg"
-- テキストファイル
set textFile to (path to desktop as text) & "目次.md"
-- フォルダ
set theFolder to path to documents folder
-- 全てをリストでまとめる
set fileList to {imageFile, textFile, theFolder}
-- Finder の open は Finder でファイルをダブルクリックした時と同じ効果
-- 結果的に目的のアプリケーションを起動しファイル表示を行う
tell application id "com.apple.finder" to open fileList
-- 画像ファイルを他のアプリケーションで開きたいならそのアプリケーションで open
-- Safari に先のリストを渡すと...
-- 画像とテキストファイルは開く
tell application "Safari" to open fileList
-- AppleScript に対応していない Day One なら?
-- 起動はするけどなにも行われない
-- ただ、Day One が起動している時に実行すると画像ファイルを添付した新規エントリ作成
tell application "Day One" to open fileList

open 命令は AppleScript で定義されている命令ですが、多くのアプリケーションで利用できます。

もう少し詳しく説明すると、AppleScript からみてアプリケーションは以下の 3 つのタイプに分類できます。

  1. AppleScript に対応している
  2. AppleScript に対応していないが、基本的な命令を受け付ける
  3. AppleScript に全く対応していない

Day One は 2 番の「AppleScript に対応していないが、基本的な命令を受け付ける」アプリケーションです。この手のアプリケーションは多く、AppleScript でちょっとした処理が出来たりします。

最後の reopen もアプリケーションの起動として利用できます。

すでに起動しているアプリケーションの Dock にあるアイコンをクリックした時と同じ挙動を行わせる命令...といえば分かりやすいでしょうか。

run 命令に似ているのですが、reopen の場合必ずなんらかの処理が行われます。多くの場合は起動時と同じ処理...例えば、新規ウィンドウを作る等。Terminal の場合、ウィンドウがない場合は新しいウィンドウを表示します。すでにウィンドウがあるならなにもしません。

まとめましょう。

単純にアプリケーションを最前面に持ってきたいなら activate を使います。

静的にアプリケーションを起動させたいだけなら launch を使います。アプリケーションを静的に起動させ、かつ、起動時の処理も行わせたいなら run と組み合わせます。

起動しているアプリケーションに起動時の処理を再度行わせたいなら reopen を使います。

アプリケーションを起動し、ついでになんらかのファイルを開きたいなら open を使うと効率的です。

注意しなければいけないのは runlaunch を起動しているアプリケーションに使ったときにどういう処理をするかはアプリケーションに依存する...というところでしょうか。実際のところこれら以外の命令であってもどのように処理するかはアプリケーション依存なのだけど。だから AppleScript でアプリケーションを操作する場合はテストが必須です。

配布されているようなスクリプトならなおさら。長いこと AppleScript を使っているけど、いまだに人が書いたスクリプトが読めないことがありますし...(これはこれで問題だ)。

アプリケーションの終了

起動したアプリケーションは終了しなければいけません(そんなことはない)。

終了は起動に比べてシンプルで quit 命令しかありません。この命令は通常の終了であり、強制終了ではありません。

quit application id "com.apple.Terminal"

ただ、アプリケーションによってはファイルの保存が必要になることがあります。ファイルが未保存の場合、多くの場合アプリケーションが保存されていないが本当に終了してもいいか?と問いかけてくることでしょう。

ときどき保存が必要なのにその確認を行わないアプリケーションもあります。こういった命令を利用するときは事前に動作確認をしておく必要があります。

また、保存は行わずに直ちに終了させたい場合があります。そういうときは quit のオプションを使います。

-- 保存されていなくても直ちに終了する
quit application id "com.apple.TextEdit" saving no
-- 保存をしてから終了する
quit application id "com.apple.TextEdit" saving yes
-- 保存するかどうか尋ねる
quit application id "com.apple.TextEdit" saving ask

状況によってこれらのオプションを使い分けます。

この命令は通常なら終了できないようなアプリケーションでも終了させることができます。分かりやすいのが Finder でしょうか。Finder は通常なら終了メニューがないので終了できませんが、quit で終了させることができます。

-- Finder の再起動
quit application id "com.apple.finder"
-- 終了処理が終わらないうちに起動命令が伝わるのでちょっと待つ
delay 1
activate application id "com.apple.finder"

Finder なら別にいいのですが、logwinwindow や SystemUIServer などという背後で動いている重要なアプリケーションでも quit を送ることができます。おそらくそれらが終了することはないのですが、なにが起きるかは分からないのでやめておきましょう。

quit 命令でよくあるのがアプリケーションの一括終了。


コメントはいつもより多めにしています。

AppleScript に対応していないアプリケーションについて

アプリケーションの起動と終了に関して見てきましたが、以上の命令は特別に AppleScript に対応していなくても使えるものです。

先にも書きましたが「AppleScript に対応していないが、基本的な命令を受け付けるアプリケーション」は AppleScript から操作できます。

では、基本的な命令とはなんでしょうか?

  1. アプリケーションを開く
  2. 書類を開く
  3. 書類を印刷する
  4. アプリケーションを終了する

これらの処理です。では、これらの処理ができるのかどうかをどうやって調べるのかというと System Events を使います。

System Events というのは OS やシステムに関する設定などを行ってくれるバックグラウンドアプリケーションです。このアプリケーションは process というクラスを持っていて、これを使うとプロセスに関する情報を調べることができます。

tell application id "com.apple.SystemEvents"
    -- Terminal が起動していないなら終了
    if not (process "Terminal" exists) then return
    tell process "Terminal"
        -- バンドル ID
        bundle identifier
        --> "com.apple.Terminal"
        -- 表示されているか隠されているか?
        visible
        --> true
        -- 最前面かどうか?
        frontmost
        --> false
        -- Unix でのプロセス番号
        unix id
        --> 16448
        -- kill してみる
        do shell script "kill 16448"
    end tell
end tell

AppleScript に関連する情報は process クラスの has scripting terminologyaccepts high level events 属性を調べます。AppleScript に対応しているなら has scripting terminology が真になり、対応していなくても accepts high level events が真なら基本的な命令を受け付けることができます。

tell application id "com.apple.SystemEvents"
    -- Terminal が起動していないなら終了
    if not (process "Terminal" exists) then return
    tell process "Terminal"
        -- 基本的な命令を受け付けるか?
        accepts high level events
        --> true
        -- AppleScript 対応か?
        has scripting terminology
        --> true
    end tell
end tell

accepts high level events が真なら activatequit といった命令は受け取れます(...と教科書通りにいけばいいのですが、実際はそうじゃないこともあります。結局、最終的には手作業で調べなきゃいけないはめになる。それが AppleScript)。

System Events にはこの他に UI Element というクラスが定義されていて、(個人的には)最終手段ともいえるプロセス自身の泥臭い操作もできます。

activateopenquit が使えればなんとかなる...と個人的には思っているのですが、世の中の大半の人はそうではないようで、この UI Element を使ってナニか面倒なことをよくやっている人が多いです。もっと楽をしようよ。

AppleScript を使う場合、そのアプリケーションに精通している必要があります(そもそもそのアプリケーションでなにができるが分からないのに操作したいなんてナンセンス)。AppleScript を使うまでもなく、アプリケーション自体に自動化できる装置がついていたりすることもあります。

Day One のようなアプリケーションは AppleScript に対応していませんが、シェルで操作できるツールが提供されていたりします(ところで、Day One 2 が出るんですね。AppleScript 対応...とかないかな)。このツールでできるのは新規作成だけですが、Day One はユーザーの Library フォルダに記事をファイルで保存しています。これは SQLite で SQLite ならシェルで操作できます。

また、有名どころの Web サービスなんかだと API が提供されていたりします。do shell script で curl 使えばなんとかなるでしょう。

Mac 版の Kindle だと AppleScript に対応していないし、そもそも API もない。それでも URL Scheme があったりするので特定のページや書籍を開くことはできます。

こういうふうに方法はいろいろあります。もちろん状況次第ですが、なるべくなら UI Elements の利用は控えておく方がいいです。すぐに使えなくなるから。

tell application id "com.apple.SystemEvents"
    tell process "Terminal"
        -- 「ターミナル」メニュー
        tell menu bar item 2 of menu bar 1
            -- 「環境設定」を表示
            click menu item 3 of menu 1
        end tell
    end tell
end tell

メンテナンスが大変なスクリプトの一例。こういうのやウィンドウのボタンをクリックするのとか。こういうのはメニューやボタンがその場にないと動かなくなるから。で、メニューやボタンなんてバージョンアップでわりと簡単に移動するから。そもそもコメントがないとなにをやっているか分からない。

最後の方はアプリケーションの起動や終了と関係ない話でしたね。といってもこのメニューを操作してアプリケーションを終了させるなんてスクリプトをわりと見かけたりします。もう、みんな大丈夫だよね。そう、こういうときは

quit を使え

最低でも kill にしておきましょう。