アプリケーションによるファイル参照

ファイルを扱うアプリケーション...、Finder や System Events でのファイル参照について。

どうも AppleScript によるファイル参照とこれらのアプリケーションによるファイルの参照がごちゃごちゃになっていて変なエラーに悩まされている...という人を時折見かけます。

AppleScript の分かりにくい部分ですね。

この違いとアプリケーションとの連携をみていきましょう。


それはオブジェクトの参照です

まずは、Finder や System Events などでのファイル指定はファイルの参照ではなく、アプリケーションが扱うオブジェクトへの参照...ということを理解しましょう。

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

tell application id "com.apple.finder"
    -- デスクトップにあるファイルを指定
    set theFile to file 1 of desktop
end tell
-- ファイルの内容を読み込む
read theFile

このスクリプトはエラーになります。もし、この参照が AppleScript でのファイルの参照ならエラーにならないはずです。

この参照は Finder のオブジェクトの参照にすぎず、ファイルの参照ではありません。まずはこのことを理解しましょう。

オブジェクトの参照

AppleScript は、アプリケーションやオブジェクトに対してなんらかの命令を送り、なんらかの処理を行う...と思っていると理解の妨げになるかもしれません。

AppleScript で行う操作の大部分はオブジェクトの属性の変更です。そのために頻繁に用いる命令は set です。AppleScript は tell 構文で処理するターゲットを指定し、set で処理を行うことがほとんどです。極端な話、tellset の使い方が分かれば、AppleScript の大半は理解できるのではないか?と思っているぐらいです。

AppleScript では処理するオブジェクトやオブジェクトの属性を指定するために頻繁に参照を利用します。もちろん、これはファイル参照とは別物です。オブジェクト参照とは、処理したいオブジェクトを特定するものです。

オブジェクト参照は、目的のオブジェクトからコンテナ(オブジェクトが入っている容れ物)へと of をつなげて作ります。

目的のオブジェクト of オブジェクトの容れ物 2 of オブジェクトの容れ物 1

デスクトップにあるフォルダの中の最初のファイルを Finder で指定するには次のようになります。

tell application id "com.apple.finder"
    file 1 of folder 2 of desktop
end tell

英語の文法的には問題ないんですけどね、これで。スクリプトの意味としては『アプケーション Finder のデスクトップにある 2 つめのフォルダの中にある最初のファイルオブジェクト』ぐらいの意味で、それ以上でも以下でもない。

通常はオブジェクトの参照を使ってファイルになんらかの処理を行います。

tell application id "com.apple.finder"
    -- 重要なファイルなのでロックしておいてね
    set locked of file 1 of folder 2 of desktop to true
end tell

このようにオブジェクトが持っている属性を変更(もしくは取得)します。

おそらく、この辺りの参照でつまづく人は Finder がファイルやフォルダの処理を行うアプリケーションだから、ファイルに対する入出力も Finder を通して行うだろう、と勝手に予測をしてしまうからではないでしょうか。

Finder を通して得た参照には Finder で行う処理以外はできません。ファイルの移動や複製、エイリアスの作成やロック、コメントの付加など Finder で行う操作はできます。逆に言うとこれらの操作は AppleScript 単体ではできません。あくまで Finder(もしくは、System Events)を通してのみできる操作です。

ただ、Finder と AppleScript の親和性は高いので、alias による参照は Finder で利用できますし、Finder のファイル・フォルダオブジェクトの参照を alias に変換することはできます。

この変換は他のアプリケーションでファイルを利用するために必要になってきます(大多数のアプリケーションでは alias 参照を通してファイルを処理します)。

tell application id "com.apple.finder"
    -- Finder の参照を alias クラスに変換
    set theFile to file 1 of folder 2 of desktop as alias
end tell
-- 他のアプリケーションでは alias による参照を使ってね
tell application "Safari"
    open theFile
end tell

alias を Finder に渡してもそのまま処理してくれます。

-- デスクトップにあるファイルの alias 参照
set theFile to alias ((path to desktop as text) & "test.scpt")
tell application "Finder"
    -- 複製を作る
    duplicate theFile
end tell

しかし、これは Finder が alias を利用できるというだけで、他のアプリケーションでもこのように利用できるというわけではありません。どこまで利用できるかはそのアプリケーションを調べてみないことにはなんとも言えません(ファイルやフォルダの処理は System Events でも同様なことができます。また、alias も扱えるので、Finder の代替としても利用できます)。

理解をさらに困難にさせるのが AppleScript の file クラスと Finder の file クラスというように同音異義語が多数登場することです。

このような同音異義語がどのような文脈で利用されているか...これを意識していないと変なエラーに悩まされることになります。

Finder でファイル参照

アプリケーションを通してなんらかの処理を行う...これが AppleScript の正しい使い方です。その処理に特化したアプリケーションを使えば効率がいいし、ぶっちゃけ楽だからです。

Finder で選択しているファイルを AppleScript の基本的な機能だけで取得するのは困難ですが、Finder を使えば一行です。

tell application id "com.apple.finder"
    -- 現在選択している項目を alias で取得
    set theseItems to selection as alias list
end tell
repeat with thisItem in theseItems
    (* 個々の項目に対しての処理を記述 *)
end repeat

また、Finder では alias list というクラスが定義されていて、ファイルやフォルダオブジェクトの参照を alias で一括して取得することができます。他のアプリケーションに渡す場合は、このようにして一括で処理できます。

最前面にあるウィンドウのファイル一覧を得るには次のようにします。

tell application id "com.apple.finder"
    -- 前面のウィンドウに表示されているファイル一覧を取得
    set theseFiles to files of Finder window 1 as alias list
end tell

Finder は、フィルタ参照(条件に合致したオブジェクトを特定するための参照方法)を使えるので、デスクトップ上にある拡張子が scpt でロックがかかっているファイルの一覧などという複雑なファイル指定も行えます。

tell application id "com.apple.finder"
    -- デスクトップにある拡張子 scpt でロックされているファイルの一覧
    files of desktop whose name extension is "scpt" and locked is true
end tell

ただ、POSIX path への変換は Finder のオブジェクトからはできないので、ちょっと手間がかかります。

tell application id "com.apple.finder"
    -- 選択項目を alias で取得
    set fileList to selection as alias list
end tell
-- POSIX パスに変換
repeat with thisItem in fileList
    set contents of thisItem to POSIX path of thisItem
end repeat

ひとてま必要なのがツラいところです。

System Events でファイル参照

System Events は Finder と同じようにファイルやフォルダを操作することができます。が、両者に互換性はなく、ちょっと癖があります。

tell application id "com.apple.systemevents"
    -- デスクトップにあるファイル一覧を取得
    set fileList to file of desktop folder
end tell

このスクリプトを実行してみると分かりますが、不可視のファイルも取得しています。Finder では不可視のファイルは無視します。

また、Finder のように一括で alias に変換するような方法もありません。その代わりに pathPOSIX path という属性があるので、この属性を使って一括で取得することができます。

tell application id "com.apple.systemevents"
    -- デスクトップにある全ファイルの HFS パスを取得
    set pathList to path of file of desktop folder
    -- デスクトップにある全ファイルの POSIX パスを取得
    set POSIXpathList to POSIX path of file of desktop folder
end tell

System Events でもフィルタ参照が使えるので Finder でやったような条件に合致したオブジェクトを取得することができます。

tell application id "com.apple.systemevents"
    -- デスクトップにある拡張子 scpt のファイルの HFS パスを取得
    set pathList to path of files of desktop folder whose name extension is "scpt"
end tell

まとめ

このようにファイルを処理する方法が複数用意されているということが AppleScript の理解の妨げになっているような気がします。

そのため、ファイル処理だけを統一的に扱うためのスクリプトを作っている、という人もいます。

個人的にはファイルやフォルダの処理には Finder を使っています。慣れているし、System Events を使うより圧倒的に楽だからです。

肝心なのはアプリケーションによるオブジェクトの参照と AppleScriptの aliasfile による参照を混同しないこと。複数のアプリケーションの連携を行うなら、オブジェクトの参照から alias の参照に変換する、もしくは POSIX パスに変換しておくといった処理をアプリケーションの境界で行っておくことです。

この辺りのことを意識しておけば、不用意なエラーに悩まされることは少なくなると思います。