その後の「文字列で指定したハンドラを実行する」

まずは、Excellent!! と言いたい。

文字列で指定したハンドラを実行する」を書いたところ、ppmweb さんと Script factry さんから有益な情報とスクリプトを送って頂きました。

正直なところ、私の方法は無意味に osacompile を使っていたり、その上 run script 命令まで使ってなんだかな、と言った趣きがありました。その後、Script factry さんがシンプルな方法を発表されていて、実用として使うならこちらかなと思っていたのですが、ppmweb さんにご教授いただいた方法もまた、シンプルなのでした。

この方法の出典は、AppleScript-Users ML です。

ppmweb さんは、この方法でレコードのラベル、スクリプトオブジェクト内のハンドラ、スクリプトオブジェクトのプロパティも文字列で取得可能な事を調べてくださり、また、load script 命令で読み込んだスクリプトのハンドラなども文字列で取得可能だという事を証明して下さいました。まさに、Great, Excellent, Beautiful and Lovely!!

今宵は気分良く酔えそうです。送って頂いたスクリプトは 2 つあって、一方がメインとなるスクリプトで、他方がメインから読み込まれるスクリプトになっています。まず、読み込まれる方のスクリプト。

Script Editor で開く

on externalShow(theText)
    display dialog theText
end externalShow

このスクリプトを externalTextShower.scpt として保存しておきます。メインとなる方のスクリプトは以下。

Script Editor で開く

(* ==== Demo ==== *)


-- Demo 1 

callHandler(me, "showText", {"I'm a top level handler."})

on showText(theText)
    display dialog theText
end showText


-- Demo 2

callHandler(TextShower, "show", {"I'm a handler of the script object."})

script TextShower

    on show(theText)
        display dialog theText
    end show

end script


-- Demo 3
-- 以下のコードの実行には externalTextShower.scpt が必要
(*set thePath to join(":", items 1 thru -2 of split(":", path to me as text)) & ":externalTextShower.scpt"
set externalTextShower to load script file thePath

callHandler(externalTextShower, "externalShow", {"I'm a handler of the loaded script file."})*)



(* ==== Call Handler Core ==== *)

on callHandler(targetScript, handlerName, handlerParams)


    set handlerParamLabels to {}
    set numberOfHandlerParams to count handlerParams

    if numberOfHandlerParams > 0 then
        repeat with i from 1 to numberOfHandlerParams
            set the end of handlerParamLabels to "handlerParam" & (i as text)
        end repeat
    end if


    set runCommandParamLabels to {"targetScript", "theHandler"} & handlerParamLabels
    set runCommandParams to {targetScript, extract_usrf(targetScript, handlerName)} & handlerParams


    run script "on run{" & join(",", runCommandParamLabels) & "}
    tell targetScript to " & handlerName & "(" & join(",", handlerParamLabels) & ")
end" with parameters runCommandParams


end callHandler


-- Taken from <http://lists.apple.com/archives/applescript-users/2006/Sep/msg00446.html>.
-- Usage Examples:
--     extract_usrf({label1:"value1", label2:"value2"}, "label1") -- "value1"
--     extract_usrf(ScriptObjectA, "propertyName") -- "value of ScriptObjectA's property propertyName"
--     extract_usrf(me, "handlerName") -- «handler handlerName»

to extract_usrf(theRecord, fieldName)
    run script "on run{r}
    return " & fieldName & " of r
end" with parameters {theRecord}
end extract_usrf



(* ==== Text Manupilation ==== *)

on split(separator, sourceText)

    tell AppleScript
        set oldDelimiters to text item delimiters
        set text item delimiters to {separator}
    end tell

    set listOfText to text items of sourceText

    tell AppleScript to set text item delimiters to oldDelimiters

    return listOfText

end split


on join(separator, listOfText)

    tell AppleScript
        set oldDelimiters to text item delimiters
        set text item delimiters to {separator}
    end tell

    set theText to listOfText as text

    tell AppleScript to set text item delimiters to oldDelimiters

    return theText

end join

こちらも名前を付け、先の読み込まれるスクリプトと同階層の場所に保存しておきます(スクリプト内のコメントにある Demo 3 でスクリプトを読み込んで実行するのですが、このとき同一階層からスクリプトファイルを指定しているので)。

では、Great で Excellent で Beautiful で Lovely なこのスクリプトがなぜ動くのかを推論とともにお送りしたいと思います(スクリプトは分かりやすいように単純化しています)。

Script Editor で開く

script MyObject
    on x()
        display dialog "MyObject"
    end x
end script

run script "on run {target}
tell target to x()
end run" with parameters {MyObject}

run script "on run {target}
tell target to x()
end run" with parameters {me}

on x()
    display dialog "me"
end x

with parameters とは気がつかなかった(馬鹿をさらけだすようですが、本当に気がつかなかった)。

文字列で指定したハンドラを実行する」で指摘したと思うのですが、run script 命令は新しいスクリプトオブジェクトを生成します(生成する...これは勝手な推論で根拠はありません)。現在実行されているスクリプトとは別の新しいスクリプトオブジェクトなので、以下のスクリプトは動かないのでした。

Script Editor で開く

set x to 10

run script "x"
--> x変数は定義されていません。

定義されていないなら定義されているオブジェクトを渡せばいいのです。もし、新しいスクリプトオブジェクトが生成されるのならば、そのスクリプトオブジェクトの run ハンドラの引数に渡せばいいのです(run script 命令は run ハンドラを実行するから)。このシンプルな発想。

  1. 文字列で run ハンドラとその引数を作る
  2. run script 命令に文字列を渡す
  3. run script 命令は文字列をコンパイルする
  4. コンパイルした時点では構文エラーにならない
  5. run script 命令がコンパイルされたスクリプト(スクリプトオブジェクト)の run ハンドラを実行
  6. run ハンドラの引数が実際のオブジェクトの参照に置き換わる
  7. MISSION COMPLETE

4 の時点で構文エラーにならないのは以下のスクリプトがエラーにならないのと同じ理由です。

Script Editor で開く

on run {target}
    tell target to x()
end run

ようするにこのスクリプトを保存し、run script 命令で保存したファイルを引数を指定して実行しているのと同じことなのです。

ppmweb さん、本当にありがとうございました。

そして、Script factory さんからはいくつかの間違いを指摘いただきました。

script Obje
end script

on x(num)
    return num
end x

--set x of Obje to x

tell Obje to x(10)

スクリプトオブジェクトにハンドラをインストールしている部分をコメントアウトしました。それでもこのスクリプトは動きます。つまり、同一スクリプト内でのハンドラの有効範囲(可視性、スコープ)です。以下のようにしても動く事からハンドラは同一スクリプト内にある限り、ほとんどの場所から参照できるのです。

ちゃらんぽらん : 文字列で指定したハンドラを実行する

まず、このスクリプトが動くのがなぜか?

引用にもあるようにスコープが原因だと書いていますが、スクリプトオブジェクト Obje が上位のスクリプトを継承しているから、という事のようでした。以下のスクリプトを動かしてみると分かります。

Script Editor で開く

script Obje
end script

parent of Obje
--> «script»
name of parent of Obje
--> "Script Editor"

ほんとだ。継承している...。だから動いたのか、と納得。って、AppleScript Language Guide に書かれていたような(うろ覚え)。そうか、継承か。

次の指摘。

読み込まれた方でハンドラが共有されます(されてしまいます)。この事を利用したのが最初に紹介したスクリプトです(蛇足ですが実際はハンドラだけでなく変数でもスクリプトオブジェクトでも文字列で指定する事ができます)。

ちゃらんぽらん : 文字列で指定したハンドラを実行する

ハンドラが共有されると書きましたが、これも間違い。

osacompile で変数名だけのファイルを書き出した場合、グローバル変数だけを持ったスクリプトを作っているのと同じになり、load script したとき、トップレベルのスクリプトのプロパティと値が共有されるから動くのだそうです。この辺りの事は、Script factory さんの侵略の global 変数 に詳しいです。参考までにいくつかのスクリプトを。

Script Editor で開く

set num to 10

x()

on x()
    set num to 20
end x

num
--> 10

次にハンドラ内でグローバル変数を定義。

Script Editor で開く

set num to 10

x()

on x()
    global num
    set num to 20
end x

num
--> 20

num 変数は共有されます。自分自身を表す my を使うと、global を利用しなくても変更できます。

Script Editor で開く

set num to 10

x()

on x()
    set my num to 20
end x

num
--> 20

しかし、num 変数をローカル変数にすると動きません。

Script Editor で開く

local num
set num to 10

x()

on x()
    set my num to 20
    --> num のタイプを reference に変換できません。
end x

num
--> 10

そして、最後の指摘。handler はローカル変数におさめる事はできなくて、property にならおさめる事ができる。だから、以下のスクリプトは動かないのでした。

Script Editor で開く

x()

on x()
    set theResult to handlerWithName("greeting")
    theResult("Mac")
    --> «script» は theResult メッセージを認識できません。
end x

on getNum()
    return 81
end getNum

on greeting(yourName)
    display dialog "Hello, " & yourName
end greeting

on handlerWithName(str)
    set dir to path to temporary items folder from user domain as Unicode text
    set fileName to "tmp.scpt"
    set scptFile to dir & fileName
    set posixFile to POSIX path of scptFile
    set str to quoted form of str

    do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
    set theReult to run (load script file scptFile)
end handlerWithName

...まだまだ分からない事も多いけど、納得。

ppmweb さん。Script factory さん。本当にありがとうございました。「ちゃらんぽらん」を続けていてよかったよ。また、このサイトをご覧になっている皆様もありがとうございます。「ちゃらんぽらん」はあなたに支えられています。

えっと...また、引用。

素敵な回答をいただいた方には、感謝の気持ちを込めて個人的にほっぺにチュッてします。おでこでも可。

ちゃらんぽらん : 文字列で指定したハンドラを実行する

ほっぺにチュッ。

おでこでも可。

えっと...。

どういたしましょう?

文字列で指定したハンドラを実行する

表題の通り。ほかの言語なんかでは文字列でメソッドを指定して実行するような機能(というか...機能ではないですね)があったりします。クラスを文字列で指定して取得とか。こういうことが AppleScript でできるのかな?できないのかな?どうなのかな、と常々考えていました。

普通に考えるとできません。できないけどしたい。そりゃ、無茶だ。でも、したい。

方法はない事もないのです。Script factry さんが 2007 年 3 月 22 日に「ハンドラを文字列で指定して実行する」としてその方法について書かれています。

この記述が公開されたときに既にハンドラを文字列で指定して実行する方法は見つけていたのですが、なにせその動作原理を説明するのが難しくて公開を控えていたのでした。いまでも説明するのが難しい。人には説明できないこのもどかしさ。

ともかく、以下のようにすればハンドラを文字列で指定して実行できます。

Script Editor で開く

set theHandler to handlerWithName("getnum")
theHandler()
--> 81

set theHandler to handlerWithName("greeting")
theHandler("Mac")

on getNum()
    return 81
end getNum

on greeting(yourName)
    display dialog "Hello, " & yourName
end greeting

on handlerWithName(str)
    set dir to path to temporary items folder from user domain as Unicode text
    set fileName to "tmp.scpt"
    set scptFile to dir & fileName
    set posixFile to POSIX path of scptFile
    set str to quoted form of str

    do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
    return run (load script file scptFile)
end handlerWithName

わはは。力技だ。実際にはハンドラを文字列で指定して実行しているのではなく、ハンドラを取得しているのですが(ハンドラは変数に入れる事ができる、という事を利用しているのです)。

これでスクリプト実行時に動的にハンドラやスクリプトオブジェクトを変えることができます。もちろん、多少の速度低下は否めませんが。というか、こういう方法よりもっと簡単な『文字列で指定したハンドラやスクリプトオブジェクトを実行、あるいは取得する』方法はないでしょうか?

何か方法があれば、blackcharan@gmail.com までご一報を。

と、このスクリプトが完成したのが 2006 年の晩秋。そして、最初に書いたように Script factry さんがスマートな方法を公開されたのでした。この方法を見た時、はたと膝を叩いてしまいましたよ。

さて、唐突ですがここで問題です。

以下の run script 命令でなぜエラーが出るのでしょうか?

Script Editor で開く

set x to 10

run script "x"
--> x変数は定義されていません。

そもそもこのエラーによって文字列でハンドラを実行する方法を思いついたのでした。素敵な回答をいただいた方には、感謝の気持ちを込めて個人的にほっぺにチュッてします。おでこでも可。

このエラーが意味する事はさらにあります。Script factry さんが公開された方法でならできる事があります。しかし、先に記述したスクリプトではこれができません。それはいったいなんでしょうか?

もう一つ。かりやんさんが書かれていた事なのですが、スクリプトオブジェクトへハンドラがインストールできるという記述。しかし、これはちょっと違うような気がします。スクリプトオブジェクトにハンドラをインストールできる(ように見える)のは、そのスクリプトオブジェクトとハンドラが同一スクリプトファイル内で定義されているときだけです。

Script Editor で開く

script Obje
end script

on x(num)
    return num
end x

set x of Obje to x

tell Obje to x(10)

一見、インストールできているように見えます。が、本当にインストールできるなら load script 命令で読み込んだスクリプトオブジェクトでも同じようにできるはずです。が、それはできません。上記のスクリプトがなぜ動くのか。以下のスクリプトを試してみれば、その原因が見えてきます。

Script Editor で開く

script Obje
end script

on x(num)
    return num
end x

--set x of Obje to x

tell Obje to x(10)

スクリプトオブジェクトにハンドラをインストールしている部分をコメントアウトしました。それでもこのスクリプトは動きます。つまり、同一スクリプト内でのハンドラの有効範囲(可視性、スコープ)です。以下のようにしても動く事からハンドラは同一スクリプト内にある限り、ほとんどの場所から参照できるのです。

Script Editor で開く

script Obje
    x(10)
end script

on x(num)
    return num
end x

tell Obje to run

load script 命令で読み込んだスクリプトオブジェクトは、この有効範囲が異なります。読み込んだ方と読み込まれた方はそれぞれ別の有効範囲を持っています。つまり、読み込んだスクリプトオブジェクトにはハンドラのインストール(?)ができないのです。

問いとして出題した run script 命令が動かないのは、この有効範囲の問題です(run script 命令で文字列をスクリプトとして実行すると新しいスクリプトオブジェクトが作成される、と考えると分かりやすい。だから、変数が未定義になるのです)。

しかし、例外があります。読み込んだスクリプトオブジェクトにハンドラがインストールできないのは、読み込んだスクリプトオブジェクトにハンドラ(変数)が定義されていないからです。逆に言えば、定義さえされていればいいのです。定義されているなら読み込んだ方、読み込まれた方でハンドラが共有されます(されてしまいます)。この事を利用したのが最初に紹介したスクリプトです(蛇足ですが実際はハンドラだけでなく変数でもスクリプトオブジェクトでも文字列で指定する事ができます)。

on handlerWithName(str)
    set dir to path to temporary items folder from user domain as Unicode text
    set fileName to "tmp.scpt"
    set scptFile to dir & fileName
    set posixFile to POSIX path of scptFile
    set str to quoted form of str

    do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
    return run (load script file scptFile)
end handlerWithName

そして、Script factry さんが公開された方法でならできる事というのは、文字列でハンドラを呼び出すハンドラを他のハンドラ内から利用する、という事です。先に紹介したスクリプトでは以下の事ができません。

Script Editor で開く

x()

on x()
    set theResult to handlerWithName("greeting")
    theResult("Mac")
    --> «script» は theResult メッセージを認識できません。
end x

on getNum()
    return 81
end getNum

on greeting(yourName)
    display dialog "Hello, " & yourName
end greeting

on handlerWithName(str)
    set dir to path to temporary items folder from user domain as Unicode text
    set fileName to "tmp.scpt"
    set scptFile to dir & fileName
    set posixFile to POSIX path of scptFile
    set str to quoted form of str

    do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
    set theReult to run (load script file scptFile)
end handlerWithName

Script factry さんが公開された方法なら、このような別のハンドラ内からの利用でも問題ありません。

Script factry さんの方法を見習って解決するなら、以下のようになります。

Script Editor で開く

script caller
    property method : missing value

    on request(argument)
        if argument is "" then
            my method()
        else
            my method(argument)
        end if
    end request

    on handlerWithName(str)
        set dir to path to temporary items folder from user domain as Unicode text
        set fileName to "tmp.scpt"
        set scptFile to dir & fileName
        set posixFile to POSIX path of scptFile
        set str to quoted form of str

        do shell script "osacompile -e " & str & " -o " & (quoted form of posixFile)
        set method of me to run (load script file scptFile)
    end handlerWithName
end script


x()

on x()
    tell caller
        handlerWithName("greeting")
        request("Mac")
        handlerWithName("getnum")
        request("")
        --> 81
    end tell
end x

on getNum()
    return 81
end getNum

on greeting(yourName)
    display dialog "Hello, " & yourName
end greeting

いずれにしてもScript factry さんの方がスマートですが...。ちなみになぜ、これが動くかはナゾです。さぁ、Let's thinking!!

AppleScript 2.0 (3)

いったいいつまで AppleScript 2.0 で話を持たせるのか。とはいうものの、変更点が多過ぎなんですよね。そんなわけで、不具合。まず、以下のスクリプト。

Script Editor で開く

tell application "Finder"
    set the folderList to folders of desktop

    repeat with thisFolder in folderList
        set thisFolder to thisFolder
        repeat with thisItem in files of thisFolder
            set label index of thisItem to 1
        end repeat
    end repeat
end tell

特別変なことをしていないと思うのですが...このスクリプトは実行してもファイルのラベルの色が変わりません。原因は、二度目の繰り返しの files of thisFolder の部分。repeat with - in - の繰り返しでこの書き方ができなくなっているようなのです(できました...よね?)。以下のようにいったん変数に入れると正しく処理されます。

Script Editor で開く

tell application "Finder"
    set the folderList to folders of desktop

    repeat with thisFolder in folderList
        set thisFolder to thisFolder
        set fileList to files of thisFolder
        repeat with thisItem in fileList
            set label index of thisItem to 1
        end repeat
    end repeat
end tell

って、これ、Mac OS X 10.4 の頃からですね。ちょっと Mac OS X 10.4 で調べてみました。いつからなんでしょうか。全く知りませんでした。Finder では複数参照も使えなくなっているし...だんだんと退化しているような気が...。

さて。フォルダアクションが一新されたようです。従来、フォルダアクションは Finder が操作を行っていたのですが、フォルダアクション専用のサーバーが追加されたようです。以下、アップルからの引用。

フォルダアクションの起動がFinderからファイルシステムに変更され、信頼性が向上しました。フォルダアクションには専用のサーバが用意され、フォルダアクションごとに新しいFolder Actions Dispatcherアプリケーションが実行されます。

アップル - Mac OS X Leopard - 新機能 - 300を越える新機能

『フォルダアクションごとに新しいFolder Actions Dispatcherアプリケーションが実行されます』とあるけど、これがよく分からない。フォルダアクション設定を起動してフォルダアクションをなんらかのフォルダに設定すると確かに Folder Actions Dispatcher はプロセスに表示されます。しかし、複数のフォルダにフォルダアクションを設定したからといって、複数の Folder Actions Dispatcher がプロセスに表示されるわけではなく、一つだけです。『フォルダアクションごとに新しい...』ってそういう意味ではないのかな?

しかし、なんらかの拍子に複数の Folder Actions Dispatcher がプロセスに表示されることがあります。こうなると、ASUG のメーリングリストで指摘があった処理が行われない状態になってしまうようです。

どういったことを行うと複数の Folder Actions Dispatcher がプロセスに表示されるのか分かりませんが、フォルダアクションに関する操作は System Events、フォルダアクション設定、Folder Actions Dispatcher の 3 種類のアプリケーションで行うことができます(いずれもスクリプタブル)。これらをスクリプトで操作しているうちに複数の Folder Actions Dispatcher がプロセスに表示されたのでした。

System Events とフォルダアクション設定は、それぞれ用語説明がかぶっているなかなか凶悪な素敵仕様です。どちらか一方は必要ないと思いますが。もし、フォルダアクションで処理が実行されない、ということがあったとき、もしかしたらプロセスに複数の Folder Actions Dispatcher があるかもしれません。こういう時はどちらもいったんプロセスを終了させて、再度フォルダアクションをオンにすると症状が改善されるかもしれません(確信はないですが)。

えーと。間違い。アップルの。先ほど引用した部分に間違いがありました。『フォルダアクションごとに新しいFolder Actions Dispatcherアプリケーションが実行されます』ってのは、勘違いか、もしくは間違い。フォルダアクションごとに新しい AppleScript Runner が実行されるのでした。先に書いたような現象が起きる可能性はありますが、AppleScript Runner がフォルダアクションを実行しているのでした。正しい引用は以下。

Enjoy greater reliability with folder actions, which are triggered by the file system instead of the Finder. Folder actions now have their own server, as each folder action now runs its own copy of the new AppleScript Runner application.

AppleScript: Features

つまり、フォルダアクション専用のサーバーってのが Folder Actions Dispatcher で、このサーバーが AppleScript Runner を管理しているのかな。ちなみにこの AppleScript Runner もスクリプタブルで、do script という命令だけを持っています。

ちなみに AppleScript ユーティリティもスクリプタブルになり、スクリプトメニューの動作をスクリプトで制御することができます。

Mac OS X 10.5 Leopard の大目玉...Scripting Bridge ですね。あんまり、触れたくないんですが。分かってないから。Apple がドキュメントといくつかのサンプルを公開しているので Ruby や Python、Objective-C を利用する人にはサンプルを見ただけで記述の仕方は理解できるのではないでしょうか。いずれの言語を利用するにしても、用語説明を参照する必要はありますが(属性や命令を調べるため)。

Objective-C は、従来からプログラムの中に AppleScript を混ぜることができたのですが、Scripting Bridge では、より Objective-C らしく簡潔にプログラムを記述することができるようになっています。

一応、リンクだけ張っておきます。

うん。手抜きだな。ちょっとだけ補足。

Mac OS には Open Scripting Architecture(OSA)と呼ばれる機構が備わっています。これは、アプリケーション間でメッセージを送受信するための機構です。OSA では AppleEvent と呼ばれるメッセージをアプリケーション間で送受信します。アプリケーションは受け取ったメッセージを解釈し、処理を行います。

このようにアプリケーション間でのメッセージの送受信を行うためにはアプリケーションが OSA に対応している必要があります。Mac OS X 上の Cocoa/Carbon アプリケーションはほとんどの場合 OSA に対応しています。

OSA に対応しているアプリケーションに AppleEvent を送信し、処理を行わせる...。実際のところ、AppleEvent を直接記述してアプリケーションに送信すれば目的は達成できます。が、AppleEvent をそのまま記述するというのはなかなかできるものではありません。そこで AppleEvent を直接記述する代わりに英語のような文法で AppleEvent を記述できるようにしました。これが AppleScript です。

AppleScript は英語のような文法で処理を記述しますが、実際に実行するときに AppleEvent に翻訳されます。アプリケーションは、結果を AppleEvent で返しますが、AppleEvent は AppleScript に翻訳されて返されます。

AppleScript と各アプリケーションの間に入って AppleEvent の翻訳を行うのが AppleScript Component です。AppleScript とアプリケーションがどのように動作しているかを以下に図示します。

AppleScript の技術的な背景

実行時の AppleEvent への逐次翻訳があるから AppleScript は処理速度が遅くなるのです(それでも、今の Intel プロセッサ搭載の Mac ではそれほどの遅さは感じませんが)。Scripting Bridge でもどこかで AppleEvent への翻訳を行っていると思われます(調べれば分かることなのですが)。この点では速度的にどの言語を使っても差はないと思います。しかし、それでも速度的には差が出てくると思います。なぜなら、それぞれの言語は AppleEvent への翻訳とは関係のない部分で速度差があるから。繰り返しやリスト(配列)の処理をとっても AppleScript よりも速く結果を出せるだろうと思います。

より、速度を求めるなら、また、これらの言語の特徴が必要なら採用はありだと思います(例えば、Objective-C なら Mac OS の全てのフレームワークを活用できるわけですし)。いわゆる、適材適所ですね。

AppleScript 2.0 (2)

えっと、不具合報告でしたね。もとい、AppleScript 2.0 の変更点ですね。

AppleScript 2.0 のリリースノートが公開されていますが、日本語訳もせうぞーさんが公開してくださっています。ありがたいことです。

application オブジェクトが強化されています。running 属性が追加され、この属性でアプリケーションが起動しているかどうかの確認が行えるようになっています。

Script Editor で開く

if (running of application "Keynote") then
    activate application "Keynote"
else
    return "Keynote does not launch."
end if

この属性は対象アプリケーションが起動していなくても取得可能です。従来の AppleScript ではアプリケーションの情報を知りたい場合、そのアプリケーションを起動する必要があった(というか、勝手に起動してしまう)のですが、application オブジェクトが強化され、本当にアプリケーションの情報(用語辞書を参照するといった)が必要になるまで極力アプリケーションの自動的な起動を行いません。

Script Editor で開く

name of application "Pages"
--> "Pages"
version of application "Pages"
--> "3.0.1"

上記のようにアプリケーションの名前やバージョンを確認するのもアプリケーションを起動することなく行えます。また、アプリケーション固有の id 属性によるアプリケーションの指定も可能です。id はバンドルの識別子、もしくは 4 文字のクリエータータイプになります。最近ではクリエータータイプを持つアプリケーションも減ってきているのでバンドル識別子を利用する方がいいでしょう。

Script Editor で開く

name of application id "wrbt"
--> "iCal"
set appID to id of application "iCal"
--> "com.apple.iCal"
launch application id "com.apple.iCal"

コメントに # が利用できるようになりました。 一行コメントです。シャープが利用できるようになったのは、Unix の実行ファイルを考慮してのようです。スクリプトに #!/usr/bin/osascript を記述しておくことができるのですね。つまり、以下のようにしてターミナルからスクリプトを実行させることができます。

まず、テキストエディタなどで以下のスクリプトを記述し、適当な名前で保存します。ここでは、「Open」として保存しました。

#!/usr/bin/osascript

tell application "finder" to open desktop

ターミナルを起動し、保存したファイルに実行権限を与えます。

$ chmod +x Desktop/Open

続けて、ターミナルで Open をコマンドとして実行します。

$ ./Desktop/Open

Finder でウィンドウが表示されると思います。これは単純な例ですが、応用次第でいろいろできるでしょう。

osascript と osacompile の詳しいことは man にゆずるとして、従来 osacompile などで利用するテキストファイルはエンコーディングに UTF-8 を利用しなければいけなかったのですが、UTF-16 とプライマリエンコーディング(ユーザーの言語環境で利用されるエンコーディング)も利用できるようになりました。

また、osadecompile というコマンドも追加されています。これは、コンパイル済みスクリプトをテキストファイルに変換するコマンドです。欲しかったんです、これ。

osadecompile に引数としてコンパイル済みスクリプトファイルを渡すと、標準出力にテキストが表示されるので、リダイレクトしてファイルなどに保存するといいでしょう。

$ osadecompile Replace\ Item\ Names.scpt > tmp.applescript

Script Editor も少し強化されています。\t(タブ)、\r(ラインフィード)、\n(キャリッジリターン)を表示できるようになっています。従来は構文確認時に置き換わっていました。これは、Script Editor の環境設定から設定できます。

Script Editor の強化で一番便利なのが path to me が正しく保存したファイルのパスを返すようになったことでしょう。これで、バンドル形式の保存方法が利用しやすくなります。しかし、バンドル形式で保存したアプリケーションを再度 Script Editor で開くと「バンドルの内容」を表示できなくなるのはなぜでしょう。

以前からそうだったのですが、最初にスクリプトバンドルで保存しておきバンドルの中に様々なファイルを入れておいてからアプリケーションバンドルで保存するとバンドル内の入れておいたファイルが消滅する仕様はやめてほしいのですが、変わっていません。

用語説明で継承した親のプロパティを表示できるようになっています。これも環境設定で変更できます。ちょっと便利かな。もう少し、親の方のプロパティを区別して見やすくしてくれると、なおいいのですが。

しかし、AppleScript Language Guide が改訂されるとあるのに、未だに公開されていない。なぜでしょう。エラーメッセージもちゃんと返ってくるとあるけど、どことなく変な日本語。だけど、ないよりはいいでしょう。プロパティリストの読み書きができるようになっているけど、XML は相変わらず読むだけで書き込みできないですね。

システム環境設定の強化とあるからてっきりシステム環境設定が強化されたのだと思っていましたが、そうではなく System Events が強化され、システム環境設定の各設定を System Events から行えるようになっています。

しかし、System Events ってどこまで用語辞書が膨張すれば気が済むのでしょうか。

利用にあたって特別難しいことはなく、AppleScript: Scriptable System Preferences を参照すればほぼ理解できると思います(手抜き)。

AppleScript 2.0 (1)

いつの間にやら Mac OS X 10.5 Leopard が発売され、Mac は Intel ベースになり、iPod touch などもでていて...隔世の感があります。一年前と状況が様変わり。どう、対処したらいいものか。いったいどれくらいの人が Mac OS X 10.5 に移行したのでしょうか?

Mac OS X 10.5 Leopard になってようやく AppleScript もバージョンが 2.0 になりました。ちらほらと不具合や仕様の変更に対する対処の話題なども見かけるようになりました。

AppleScript 2.0 の最大の話題は、ユニコードをほぼサポートしたことでしょうか。文字には id 属性なるものがつくようになりました。

id of "a"
--> 97

結果は、ASCII number 命令と同じですね。もちろん、この id から文字を得ることもできます。

string id 97
--> "a"

文字列でも可能。

set strIDs to id of "Love"
--> {76, 111, 118, 101}
string id strIDs
--> "Love"

ユニコードをサポートしてしまったので、日本語でも可能。

set strIDs to id of "強敵=友"
--> {24375, 25973, 65309, 21451}
string id strIDs
--> "強敵=友"

結果、以下のようなエラーが出現。

ASCII character 130
--> いくつかのデータを期待されるタイプに変換できません。

単にバグかもしれないですが。ASCII character や ASCII number といった命令は、今後 id 属性の利用に置き換わっていくのでしょう。StandardAdditions.osax には両命令に deprecated の記載がありますし(ちなみに deprecated な命令は他にもあって、info for、list disks、list folder が該当します)。

そして、text、string、Unicode text とあったクラスが全て text クラスになってしまいました。string と Unicode text は予約語として残っていますが、これらは全て text クラスで統一されています。

set theText to "abc"
set uni to theText as Unicode text
set str to theText as string

class of uni
--> text
class of str
--> text
uni is str
--> true

クラスも text ですし、比較も真になります。クラスが統一されたので、クラスによる比較には気をつける必要があります。

set str to "a"
if (class of str) is string then
    --
else if (class of str) is Unicode text then
    --
else
    --
end if

このような書き方は可能ですが、AppleScript 2.0 では string = text = Unicode text なので上記の分岐には意味がありません。

ユニコードのサポートはファイル入出力にも関係してきます。ファイルへの文字列の書き出しは、as で文字列の型を指定することになりますが、as で指定できるのは text か Unicode text、もしくは指定しない(as をつけない)になります。

as Unicode text とすれば、UTF-16 での書き出し。as text とすれば、ユーザーのプライマリエンコーディングを使用します。Mac を日本語環境で使っているとプライマリエンコーディングは「日本語(Mac OS)」になります。as の指定がなしだとプライマリエンコーディングで書き出すようです。

もちろん、読み込み(read 命令)も、as で正しい型を指定しないと文字化けします。

こういったユニコードサポートに伴い Script Editor も対応がなされています。いままでは文字列で日本語を記述するときは「環境設定」のフォーマットでフォントを変えないといけませんでしたが、その必要はなくなりました。また、演算子などの特殊記号もきちんと入力できるようになっています。また、AppleScript 2.0 上ではスクリプトの中身はユニコードで保持されるようになっています。

Finder の日本語が交じったファイル名やフォルダ名も一応扱えるようになっています。濁点、半濁点混じりの日本語では文字の欠落が見られたのですが、大丈夫なようです。しかし、以下のスクリプトが動いてしまうのは解せません。

tell application "Finder" to open folder "パナマ" of desktop

一見、何の変哲もないスクリプトですが、デスクトップ上にあるのは「ぱなま」というひらがなのフォルダなのです。ひらがな、カタカナ関係なしでユニコードサポートかよ...。これは、10.4 から続いていますが、比較もひらがな、カタカナ関係なしです

"だ" is "ダ"
--> true

これに対処する方法は...分かりませんでした。

Finder 上の濁点、半濁点の文字列は扱えるようになっていますが、AppleScript's text item delimiters の方はどうも駄目なようです。

Mac OS X をインストールすると /Library/Scripts 以下にいくつかの AppleScript ファイルも同時にインストールされます。この中の Finder 関連のスクリプトのファイル名やフォルダ名を処理するスクリプトはそのままでは動きませんでした。これは、AppleScript's text item delimiters が文字列の型を見ているからで、Unicode text と string が混在しているために正しく動作しないのでした。正しく動作させるため、as string として文字列の型を統一しておけば、一応日本語環境で動いていたのでした。

Script Editor で開く

on replaceText(findChar, replaceChar, theText)
    set findChar to findChar as string
    set replaceChar to replaceChar as string
    set theText to theText as string

    set old to the AppleScript's text item delimiters
    set the AppleScript's text item delimiters to findChar
    set theList to text items of theText
    set the AppleScript's text item delimiters to replaceChar
    set theText to theList as text
    set the AppleScript's text item delimiters to old

    return theText
end replaceText

このような感じです。が、先にも書いたように AppleScript 2.0 になって string も Unicode text も同じ text クラスとして扱うため文字列の型変換が行えなくなり、上記のスクリプトのように型を統一するという方法が通じなくなったのです。ですから、以下の付属 AppleScript ファイルは日本語混じりのファイルやフォルダに対して期待した動作を行いません。

  • /Library/Scripts/Finder Scripts/Replace Text in Item Names.scpt
  • /Library/Scripts/Finder Scripts/Trim File Names.scpt
  • /Library/Scripts/Finder Scripts/Trim Folder Names.scpt

フザケンナヨ、オイ。ユニコードサポートっていったいどういう意味なんだろう。今日は夕日がやけに目に染みるゼ...。

一応代替として iconv を使った方法をあげておきます。

Script Editor で開く

(*
デスクトップに「Appleフォルダ」、「りんごフォルダ」、「アップルフォルダ」と
いった名前のフォルダがあるとして。
*)


set the desktopFolder to path to desktop folder

tell application "System Events"
    set the folderList to folders of desktopFolder
    set theList to {}
    repeat with thisItem in folderList
        set end of theList to my replaceText("フォルダ", "", name of thisItem)
    end repeat
end tell

theList
--> {"Apple", "りんご", "アップル"}

on replaceText(findChar, replaceChar, theText)
    try
        set findChar to iconv(findChar)
        set replaceChar to iconv(replaceChar)
        set theText to iconv(theText)

        set old to the AppleScript's text item delimiters
        set the AppleScript's text item delimiters to findChar
        set theList to text items of theText
        set the AppleScript's text item delimiters to replaceChar
        set theText to theList as text
        set the AppleScript's text item delimiters to old
    on error eMessage number eNumber
        set the AppleScript's text item delimiters to {""}
        display dialog (eNumber & " : " & eMessage) as text
    end try

    return theText
end replaceText

on iconv(theText)
    do shell script "echo " & quoted form of theText & " | iconv -f UTF-8-MAC -t SJIS"
end iconv

iconv では、UTF-8-MAC から SJIS に変換していますが、これは Finder や System Events が返す値を想定しているので、他の場合には適宜修正する必要があります。一応、Mac OS X 10.4 以降の日本語環境で動きます。

余談ですが、AppleScript の文字列処理で何が一番苦痛かというと、他の言語環境のことを考慮したとき。例えば、英語環境にしたらそれだけで上記のコードは動かなくなったりします。多言語、国際化という話題は AppleScript では苦痛以外のなにものでもなく、ましてや AppleScript Studio が絡むとなれば...。本来ならしなくていい苦労が多いから AppleScript Studio は流行らないのかもしれませんね。