Finder Toolbar Scripts について

前の記事で Finder Toolbar Scripts を公開したのだけど、まさか、影響を受けた方にご紹介をしていただけるとは思っていませんでした。わかばマークのMacの備忘録さんが詳細な使い方を書いて紹介してくださっていました。他人任せはいかんよな...と少し反省。そんなわけで、同梱の Read Me に書かなかったことをいくつか。

いずれのスクリプトも「実行専用」ではありません。中身を見てご自由に改変してくださって結構です。個人の利用の範囲内で。

分かりにくいかなと思っていましたが、やっぱり分かりにくかった「Favorite Window」(なら、どうにかしろよと思わないでもない)。最前面の Finder window の設定(Finder の「表示オプションを表示」メニューで変更できるいくつかの設定)を覚えておくためのものです。初回起動時に設定を覚えます。Finder ではいつも 1 つだけウィンドウを表示させて使っているのですが、閉じて、再度ウィンドウを表示させたときに以前の設定が再現されないことにストレスが溜まり作ったものです。その昔あった Apple で配布されていた Finder Toolbar Scripts に含まれていた Snapshot というスクリプトに啓発されたものです(といって、このスクリプトの存在を知っている人も少なくなったでしょうね)。

「Go to Package Folder」は、パッケージ形式のファイルの内容を新しい Finder window で表示するスクリプト。うちでは、rtfd などのパッケージ形式のファイルでも大丈夫だったのですが、開かなかったでしょうか(誰に聞いているのだ)?

「Grouping」は修正日を指定し、それに合致したファイルとフォルダを別のフォルダに移動してまとめるスクリプトです。デフォルトでは別のフォルダは今日の日付になりますが。これは、ユーザーが選択したフォルダに移動させることができます。

スクリプトを Script Editor で開くと最初に以下の属性が宣言されています。

property usesSelectSimilarItems : false
property groupingFromUserFolder : false
property groupingByModification : true

usesSelectSimilarItems を、true にすると Select Similar Items と同じような挙動を行います。groupingFromUserFolder を true にすると フォルダ選択ダイアログが表示され、ユーザーが選択したフォルダに項目を移動するようになります。groupingByModification を false にすると項目を作成日で検索するようになります。

usesSelectSimilarItems が true の場合は、選択項目が優先されます。修正日(作成日)は無視されます。

property usesSelectSimilarItems : true
property groupingFromUserFolder : true
property groupingByModification : true

このように変更すると、現在選択している項目と同じような項目をすべて選択し、それら全てをユーザーが選択したフォルダに移動させることができます。

property usesSelectSimilarItems : false
property groupingFromUserFolder : true
property groupingByModification : false

このように変更すると、作成日で項目を検索し、ユーザーが選択したフォルダに項目を移動させます。...うまく、伝わっているでしょうか? この説明...。とにかく、スクリプト先頭の上記の属性を変更すれば挙動を変えることができます。お試しくださいませ。

「Reduce File Size」は PDF ファイルに Quartz Filter を適用するスクリプトです。これもスクリプトを Script Editor で開いて先頭の

property usesUserFilter : false

を true に変えることで他の Quartz Filter を利用することができます。OS にはいくつか Quartz Filter がインストールされています。それらのなかに Reduce File Size.qfilter とこのスクリプトと同じ名前のものがあります。こちらの方がファイルサイズは抑えられるかもしれません。

後、このスクリプトはドロップレットです。Quartz Filter を適用したい PDF だけをこのスクリプトに放り込んでください。ちなみに、ファイルサイズが大きくなってしまうのはバグではありません。なぜか Quartz Filter では、こうなってしまうようです。

「Select Similar Items」は、macosxhints.com で以前に取り上げられていたものです。その着眼点はいいと思ったのですが、肝心のスクリプトはそりゃないだろう、と思ったので作り直したものです。が、おそらく多くの人は「Select Similar Items」の中身を見て「そりゃないだろう」と言うだろうと思います(何で、この目的のためにそんな面倒なことをしているのかと)。

Finder window をカラムで表示していると JPEG ファイルだけを選択するのが苦痛だったりします。名前順で並んでいて、点在しているので。

例えば、JPEG ファイルを 1 つだけ選択し、「Select Similar Items」を実行すると全ての JPEG ファイルを選択します。これで、ファイルをまとめて移動するのが楽になります。

拡張子を見て動作するので拡張子がない場合は...どうなるのでしょう?

「Update」は Finder 項目の強制的な更新を行うスクリプト。アプリケーションを作っているとアイコンが反映されなかったりすることがあるので、そういうときに使います。それ以外に使い道はあるのか、と今ふと思いました。どうなんでしょう?

Mac OS X 10.4 でも動くのですが、動けないようにしてしまったりしています。以下のスクリプトが中身なのですが、これはそのまま 10.4 で利用できます。

tell application "Finder" to update items of "対象となるフォルダ" with necessity

ASU はできるだけ目立たないように存在していますので、そのままそっとしておいてください。詳しくは Read Me を読んでください、としか言えないです。

なんで、わざと不便な設定で配布しているんだ、という突っ込みが聞こえそうです。まぁ、スクリプトを開いて中身をのぞいてみるときの楽しみというか、なんというか...。

では、お楽しみください。

Finder Toolbar Scripts

わかばマークのMacの備忘録さんの Finder Toolbar Scripts の記事を見て、脊髄反射的ににいくつか Finder Toolbar Script を作ってしまった。

Finder Toolbar Scripts

何が一番面倒だったかというと、Read Me ファイルを作ることだったりします。なんで、そんなところに時間をかけるんだよう。

で、そうこうしているうちにわかばマークのMacの備忘録さんは、さらにバージョンアップしたものを公開していたり...。

通常、ここで簡単にスクリプトの説明等をしておくのがいいのでしょうが、先にも書いたように Read Me に時間をかけてしまったため、もう気力がないです。

Read Me を読んでください。

Charan's Finder Toolbar Scripts

CUPS-PDF と AppleScript

CUPS-PDF という仮想(?)プリンタがあります。これが、便利だったりします。CUPS-PDF を使うと印刷時の出力が PDF になります。分かりにくいですが、Mac OS X のプリントダイアログにある「PDF として保存...」メニューのように印刷した書類が PDF で保存されるようになります。

詳しくは LinuxでPDFファイルを作成するには - @IT

これだけなら、プリントダイアログの「PDF として保存...」メニューでいいじゃないか、と思われるかもしれません。しかし、この作業を AppleScript で自動化しようと思ったとき、どうすればいいでしょうか?

UI Scripting ができるから不可能ではないのですが、面倒です。CUPS-PDF を使うとこの作業を少し楽にすることができます。

ともあれ、CUPS-PDF をインストールします。CUPS-PDF Package for Mac OS X から Version 2.4.6.1 をダウンロードし、リンク先に書かれている通りにインストールしてもらえば大丈夫です(Mac OS X 10.5 Leopard で試しています)。が、分かりにくい部分があるので解説を。

先のリンクから zip ファイルをダウンロードし、解凍するとインストーラパッケージが出現します。このパッケージをダブルクリックし、インストールを行います。

次に「システム環境設定」の「プリントとファックス」を表示します。プリンタ一覧の下に「+」ボタンがあります。これをクリックします。

「システム環境設定」の「プリンタとファックス」

プリンタ選択ダイアログが表示されます。ダイアログのツールバーにある「デフォルト」クリックします。CUPS-PDF が表示されているのでこれを選択します(表示されるまで少し待つかもしれません)。

「デフォルト」を指定し「CUPS-PDF」を選択する

ダイアログ下部の「ドライバ」メニューから「使用するドライバを選択...」をクリックします。

ドライバの選択

ドライバの一覧が表示されるのでその中から「Generic postscript color printer」を選択します。

Generic postscript color printer を指定

ダイアログの追加ボタンをクリックします。これで CUPS-PDF プリンタが追加されました。

「システム環境設定」の「プリンタとファックス」でデフォルトプリンタを指定

仕上げとして、「デフォルトのプリンタ」に CUPS-PDF を指定しておきます。ここまでできたら一度、何らかのアプリケーションで「プリント...」メニューを選び、プリントダイアログを表示させてみてください。

「プリンタ」に CUPS-PDF が選択されていることを確認して、そのまま「プリント」ボタンをクリックしてみてください。デスクトップに「cups-pdf」というフォルダが作られ、その中に PDF ファイルが出力されます。これだけでも楽になるのですが、ここまできたら、もう少しです。AppleScript で操作するためにもう少し設定を行います。

何らかのアプリケーションから再度「プリント...」メニューを実行し、プリントダイアログを表示します。このダイアログのポップアップメニューから「カラーマッチング」を選択し、「プリンタのカラー」を選択します。

プリントダイアログのカラーマッチングの設定

この設定を「プリセット」メニューにある「別名で保存...」で、名前をつけて保存します(ここでは「cups-pdf-color」として保存しました)。

プリントダイアログのプリセットの設定

これで完了。AppleScript を使って書類の PDF 保存が一気に楽になります。例えば、Safari で閲覧している Web ページを PDF で保存するには以下のようなスクリプト。

Script Editor で開く

tell application "Safari"
    print document 1
end tell

これだけ。これでデスクトップにある「cups-pdf」フォルダに Web ページが PDF で保存されます。

もし、上記のスクリプトがエラーになるようでしたら、プリントダイアログのプリセットで先ほど保存した「cups-pdf-color」というプリセットが指定されていないかもしれません。このプリセット(というか、「カラーマッチング」で「プリンタのカラー」を使用する)を指定していないとエラーになるようです。プリントダイアログからの印刷ではエラーにならないのだから不思議としか言いようがないのですが。

たいていの書類を編集するようなアプリケーションでは AppleScript からの print 命令を受け取れるので上記のスクリプトのアプリケーション名を変更するだけで編集中の書類を PDF で保存することが可能になります。

ちなみにこの記事の元ネタは The Joy of ScriptingBatch converting *.webloc files to PDF using Safari and cups-pdf at The Joy of Scripting です。こちらの方が分かりやすいし、他にも興味深いことが多々書かれています。

ところで、The Joy of Scripting と聞いて The Joy of Sex を思い浮かべるのは私だけでしょうか?

Safari の RSS アイコンをクリックしたら...

「Safari の RSS アイコンをクリックしたら、使っている RSS リーダーで登録をしてほしいよね」というお話。

Safari RSS アイコン

Safari の RSS アイコンっていうのは、アドレスバーの右側に出てくるやつです。

利用している RSS リーダーがローカル環境で動かすアプリケーションならそれほど面倒なことではないのかもしれない。Safari の環境設定のデフォルト RSS リーダーに指定すればいいのです。だけど使っているのが Web アプリケーションだったなら?

具体的にいうと livedoor Reader。RSS アイコンをクリックしたら livedoor Reader で RSS の購読を行いたい。Firefox なんかだとそういう設定ができた。Bookmarklet を使え、という話もあるけどそこはそれ。大人の事情により、触れることはいたしません。

Safari の RSS アイコンをクリックすることで livedoor Reader に登録することは簡単にできます。やり方は以前から知っていた(Safari の RSS アイコンをクリックしたら Bloglines に登録されて欲しいよね : blog.imladris.jp)。

では、試してみましょう。

まず、以下のようなスクリプトをバンドル形式のアプリケーションとして保存します。

Script Editor で開く

on open location thisURL
    display dialog thisURL
end open location

保存したらバンドルの中にある Info.plist に以下のように追加の記述を行います。

<key>CFBundleIdentifier</key>
<string>com.DeveloperName.ApplicationName</string>
<key>CFBundleURLTypes</key>
<array>
    <dict>
        <key>CFBundleURLName</key>
        <string>RSS URL</string>
        <key>CFBundleURLSchemes</key>
        <array>
            <string>feed</string>
        </array>
    </dict>
</array>

Info.plist を保存し、いったんログアウトし、再度ログインします。

Safari の環境設定でデフォルト RSS リーダーに Info.plist を書き換えたこの AppleScript アプリケーションを指定します(一覧に表示されると思います)。

Safari「デフォルト RSS リーダー」環境設定

これでアドレスバーの RSS アイコンをクリックすると、この AppleScript アプリケーションが起動し、渡された URL を表示します。

RSS アイコンクリックで表示されたダイアログ

ここまで確認できたら本来の目的の livedoor Reader に登録するのは簡単です。

Script Editor で開く

on open location thisURL
    if thisURL starts with "feed://" then
        set num to offset of ":" in thisURL
        set targetURL to "http" & (text num thru -1 of thisURL)
        -- display dialog targetURL
        continue open location "http://reader.livedoor.com/subscribe/" & targetURL
    end if
end open location

渡される URL は feed で始まっているのでこの部分を http に変更します。そして、livedoor Reader の URL にくっつけて open location で開くと、livedoor Reader の登録画面に移動します。

Scripting Bridge

Scripting Bridge 面白い。普通(?)の AppleScript に飽きた人はぜひ。以下、覚え書き(環境は Mac OS X Leopard。当然だけど...)。なんで Python なのかは秘密です。

Terminal.app を起動して、

$ python

と、打ち込む。インタープリタが起動するので、まず、以下の呪文を唱える。

>>> from ScriptingBridge import *

これで、Scripting Bridge に関するモジュールが取り込まれるので、次に操作したいアプリケーションオブジェクトを作成する。

>>> finder = SBApplication.applicationWithBundleIdentifier_("com.apple.finder")

Python: unknown type name "ICN#"... と、ずらずら出てくるけど、気にしない。Finder の home にあるフォルダを取得してみる。

>>> homeFolders = finder.home().folders()
>>> for thisFolder in homeFolders:
...     thisFolder.name()
... 
u'Applications'
u'backup'
u'Desktop'
u'Documents'
u'Downloads'
u'Library'
u'Movies'
u'Music'
u'Pictures'
u'Public'
u'Sites'

おほぅ(おほぅって...)。でました。って、ここまでくるのに結構時間がかかっていたり。ちょっと、違和感がある。name() なんて書かれてると、属性を取得しているように見えない。なぜか分からないし、調べてもいないのだけど、Python では属性の取得でも () が必要みたい。

フォルダでも作ってみる。まず、Foundation を取り込む。

>>> from Foundation import *

まず、フォルダの属性を辞書で作る。

prop = {'name' : 'testing'}

この属性からオブジェクトを作る。

>>> folder = finder.classForScriptingClass_("folder").alloc().initWithProperties_(prop)

これは、Objective-C ですね。init で作っているけど、メモリとかどうなるのか知らない。ガベージコレクションがあるからいいか。そして、作る。

>>> finder.desktop().folders().addObject_(folder)

ここではデスクトップに作ってみました。Finder には make 命令があるのに、なぜ make 命令を使わないか?

実は、Scripting Bridge では操作するアプリケーションのオブジェクトを作れないのです。folder を作ったではないか、と思うかもしれませんが、Finder のオブジェクトではないのです。

>>> type(folder)
<objective-c class SBProxyByClass at 0x19e15e0>

SBProxyByClass というものなんですね(SB という接頭辞は Scripting Bridge のこと)。で、結局、Finder のデスクトップにあるフォルダのリスト(配列。SBElementArray)を、まず、取得するのです。そして、このリストに追加するのです。すると、Finder でフォルダが作成されるのです。なぜか。

Objective-C マジックですな。

しかし、insertObject:atIndex: が使えないんですね。iTunes のサンプルでは使えるようですが。だから、ここでは addObject:。

先の name() ではないですが、Objective-C のメソッドを使うのに Python では、アンダースコアを使う必要があるみたい。

insertObject_atIndex_(folder, 0)
addObject_(folder)

といった感じ。コロンをアンダースコアに置き換えるのかな(ちゃんとドキュメント読みなさいって)?

Finder で一番使うものって selection でしょう(勝手に決めつけていますが)。この selection は悩みました。だって、リストが返ってくると思うじゃないですか。

>>> curSelection = finder.selection()
>>> type(curSelection)
<objective-c class SBObject at 0x19e1560>

なんで、SBObject なの?

selection を取得して繰り返しで処理...みたいなことをどうやって書くのか悩んだね。全く。結局、以下のようにすればいいのでした。

>>> for thisItem in curSelection.get():
...     print thisItem.name(),
... 
music top testing

SBObject の get メソッドを使ってオブジェクトの参照を取得するとタプルで選択項目が返ってくるので、これを使って処理をすればいいということみたい。

繰り返して処理するためには set 命令が使えないといけない。が、set 命令はそもそも使えない。どうやるのかといえば、以下のようにする。

>>> curSelection = finder.selection()
>>> for thisFolder in curSelection.get():
...     thisFolder.setLocked_(1)
... 
>>>

オブジェクトが持っている属性(locked)に set をくっつけて続く属性の先頭文字を大文字にする。setLocked_(value) となる。name 属性なら setName(value)、label index 属性なら setLabelIndex(value) となる。value はそれぞれ、真偽値や整数値などの属性が受け取る事ができる値になります。

とりあえず、オブジェクトの作成と属性の取得、変更ができました。最低限はいちおう分かったのですが、ScriptingBridge で使えることができるアプリケーションの命令やオブジェクトなどはどうやって調べるといいのか?

Script Editor で表示される用語辞書だと楽でいいのですが、利用できるものに違いがあるのでそうもいきません。スクリプタブルアプリケーションがもっている用語辞書を Objective-C のヘッダファイルに変換することで Scripting Bridge で利用できるオブジェクトや属性を調べる事ができます。

Terminal.app を起動し(先ほどの Python インタープリタのままなら Control - D でインタープリタを終了させます)。おもむろに、以下のようにタイプ。

$ sdef /System/Library/CoreServices/Finder.app | sdp -fh --basename Finder --bundleid com.apple.Finder

これでカレントディレクトリに Finder の Objective-C ヘッダファイルが作成されます。このファイルを開くとクラスやメソッドが確認できます(他のスクリプタブルアプリケーションの場合は...って上記を見ればだいたい分かる...かな?)。

以上、Python での Scripting Bridge でした。コードをいろいろ見てみましたが、Objective-C でも Ruby でもそんなに大差はないような感じですね。しかし、上記のような Python のサンプルの少ないこと、少ないこと。PyObjCappscript はたくさんあるのに...。

参考

思い出

高木重朗の「マジック入門」は思い出深い書籍である。

この書籍を読んでいなければ、手品に興味を抱くこともなかったと思う。

しかし、小学生にとってこの書籍はあまりにも難しすぎた。だって、1980 年頃だべ。地方都市だべ。近くにそれを実演して見せてくれる人もいないのだべ。

クラシック・パームなんて本当にできるのか?本当に手品の種がこんなものなのか?これが、本当の手品の秘密なのか?といった疑問に答えてくれる人など周辺には皆無だった。

案の定、長い間クラシック・パームのやり方を間違えていた...。

今ならこれら全ての疑問に答えることはできるし、この書籍の価値も分かる。だからこそ思うのだけど、子供には高度だよな、と。

だけど、この書籍で覚えて今でもよく行う手品がある。輪ゴムを使ったものとキー・カードを使ったそれである。

子供の頃にいろんな人に行った。こちらがびっくりするほどに見せた相手は驚愕する。「なんでこんな当たり前のことがふしぎなんだろう?」。「なにが不思議なんだろう?」と、子供心に奇妙に思ったことを良く覚えている。

手品の種ってそんなものなんだけど。

(明かしたくなる誘惑はあったけど)種は明かさなかった。

この書籍、本当に内容が高度で難しかった。本当にこんなことできるのか?とたびたび考えては飽かず読んでいた。しかし、子供のことである。興味は次第に他のことに移り、読む頻度も少なくなり、いつか、捨ててしまった。

今、思えば「すげー、もったいない」ことをしたものである。本当の価値が分からなかったのである。今、読みたいと思っても既に絶版になっている(わりと手に入りやすい古書の部類だけど)。

私にとって思い入れの深い書籍なのである。いまなら、インターネットが身近なものになり、手品の専門的な書籍だって簡単に手に入れる事ができるし、実演だって YouTube などで簡単に見る事ができる。

素朴にうらやましいと思う。

この書籍と出会った小学生のあの頃、どれだけこれらの情報に渇望していたことか。隔世の感がある...。

現在の情報過多に批判的な向きもあるけど、情報が無いよりはいいのである。情報をどう利用するかは使う人の問題であって、情報それ自体に良いも悪いもない。

とはいうものの、現在の自分がこの情報過多の中にあって子供の時より手品に情熱を燃やしていたり、満足したりしているのかといえば、そうでもなかったりするのが「言ってることとやってることが無茶苦茶じゃん」というところなのだけど。この辺りが人間という生き物の面白いところでもある。

人間を「矛盾を内包しているもの」と表現したのは誰だっかか?

...と、とりとめもないことを書いたけど、たまにはこういうのもいいかな?

Language Guide

新版の AppleScript Language Guide は、内容としては Mac OS 9 から Mac OS X 10.4 でも通用するものだけど、Mac OS X 10.5 Leopard 以降の AppleScript 2.0 をベースに書き直されている。

用いられている説明は AppleScript 2.0 でのもので、以前のバージョンのことと対比しながらの説明は行われていない。このドキュメントが対象としているのは Leopard 上の AppleScript 2.0。もし、以前のバージョンの詳細を知りたければ、AppleScript Release Notes (Mac OS X 10.4 and earlier) を見よ、とある。

のっけから愕然としてしまいますね。

さて、新版の最初の章は Introduction というタイトルで AppleScript の紹介を行っていますが、先に書いたようにこのドキュメントが AppleScript 2.0 以降を対象にしていることを確認しておけば十分ですね。

次の章が AppleScript Lexical Conventions。AppleScript 言語の概観といった体裁になっています。以下、駆け足で。

まず、AppleScript が扱うキャラクターセットのことについて書かれています。どんな文字を使って AppleScript を記述する事ができるか?ですが、ご存知の通り AppleScript 2.0 はユニコードに対応してしまったので『正しく全世界の文字を扱う事ができる』らしいです。

つまり、スクリプト内のコメントや文字列定数に各国の言語を使うことができるということですね。もちろん、私は信じませんが。

AppleScript の構文は半角アルファベットと、いくつかの特殊記号で記述します。特殊な記号というのは文の継続を表すソフトリターンとか、AppleScript で定義されていない生のデータを表す «» とかのことです。

AppleScript で変数やクラス、属性のラベルを識別する文字列について。これらは半角英数字とアンダースコアで定義します。

AppleScript では変数の大文字小文字を区別しないので以下のような変数は全て同じものを表します。

myName
MyName
myname

ところで、Script Editor で以下のようなスクリプトを書いて構文確認を行います。

set myName to "Alan"

一度 myName として変数を定義し構文確認を行うと、以降スクリプトの他の場所で myname と小文字で書いておいても構文確認時に myName と変数名を最初に出てきた変数名にあわせてくれます(大文字小文字を変換し、最初に出てきたものに合わせるのです)。これは、便利な反面、面倒な自体も引き起こします。

もし、MyName と変数を定義しておいてから myName に変更したいと思っても構文確認時に MyName に変換してしまいます。MyName を myName に変えたいなら、スクリプトの中に出現する全ての MyName を削除してから Script Editor を終了し、再度スクリプトを開き、myName をタイプしていかないといけない。変数名の後からの変更ってちょっと面倒なのです(そんなことない?)。

閑話休題。また、数字で始まる変数は利用できません。以下は、利用できない変数の一例です。

9th_item
C-
Try&Error
Do^and^Don't

しかし、AppleScript にはどんな文字列でも変数に利用してしまうことができる秘密の方法があります。縦棒(|)を文字列の前後に付け加えると、半角英数字とアンダースコア以外の文字や記号でも変数として扱うことができます。

|9th_item|
|C-|
|日本語|
|Love\|and\|Peace|

日本語でも構いません。これらは全て変数として扱えます(実際に使うかどうかは別にして)。変数の中に縦棒(|)を含みたい時は、最後の例のようにバックスラッシュでエスケープします。

こんなもの使う場面があるのか?とお思いになるかもしれません。時々見かけるのは、レコードのラベル名での利用。また、既に他のアプリケーションでその語彙がクラスとして定義されている場合など、衝突を避けるために利用したりします。

Script Editor で開く

tell application "Finder"
    set |folders| to folders of home
end tell

このサンプルはサンプルのためのサンプルですが、スクリプトオブジェクトを作っているとこういう場面がままあります。

そして、予約語。AppleScript では全ての予約語がアルファベット小文字だけで構成されています。また、いくつかの予約語は aside from のように単語のペアになっているものがあります。以下、予約語の一覧。

apart from, and, against, after, above, about, before, back, at, aside from, as, around, between, beside, beneath, below, behind, beginning, contains, contains, contain, considering, by, but, else, eighth, does, div, copy, continue, exit, every, error, equals, equal, end, from, fourth, for, first, fifth, false, ignoring, if, global, given, get, front, its, it, is, into, instead of, in, my, mod, middle, me, local, last, or, onto, on, of, not, ninth, ref, put, property, prop, over, out of, second, script, returning, return, repeat, reference, tell, some, sixth, since, seventh, set, through, third, then, the, that, tenth, true, transaction, to, times, timeout, thru, with, whose, while, where, until, try, without

AppleScript のコメントについて。PDF の方の AppleScript Language Guide 間違ってますね。

コメントには複数行のコメントと一行だけのコメントの 2 種類あります。

Script Editor で開く

(*
    This is the comment.
    AppleScript Guru?
    Ha, ha, No!!
    I am a AppleScript fanatic believer!
*)

これが複数行にわたるコメントの書き方でコメントの前後を (* と *) で囲みます。

一行コメントは文頭にマイナス記号を 2 つつけます。

Script Editor で開く

-- one liner

AppleScript 2.0 以降ではシャープも一行コメントに使えます。

Script Editor で開く

# 一行コメント
#!/usr/bin/osascript

シャープ(#)が使えるのは最後の例のようにシェバングとしての利用を考慮したものだと思われます。このコメントは AppleScript 2.0 以前でもコンパイル済みスクリプトを実行するだけならそのまま利用できます。しかし、Script Editor などでスクリプトを開き、編集などを行うとこのコメントはエラーを発生させます。実際に確認はしていませんが、シャープで始まるコメントがあるスクリプトを Mac OS X 10.4 などに持っていても実行する分には支障はないのですね。ただ、編集時にはエラーになると。

コメントはネストさせることができます。

Script Editor で開く

(*
This is the comment.
AppleScript Guru?
Ha, ha, No!!
I am a AppleScript fanatic believer!
-- one liner
# 一行コメント
#!/usr/bin/osascript

    (*
        これもコメント
    *)
*)

コメントってネストできました?知りませんでした。

AppleScript は、冗長です。或は、冗漫といってもいいかもしれない。冗長と冗漫。どちらも同じような意味かもしれない。

AppleScript は、英語に似た文法を持つため、どうしても一行の記述が横に長くなっていきます。こういうときにソフトリターンを使って一行を分割する事ができます。

Script Editor で開く

tell application "Finder" to ¬
    folders of home

最初の行の最後にある「¬」が次の行の継続を表すソフトリターンです。見た目は二行ですが、AppleScript は一行として解釈します。「¬」は、Option + l(小文字の L )で入力できます。

ちなみに、この継続を表すリターンは Option + Return で入力できるはずなのですが、日本語環境では化けた文字が挿入されます。これは、継続を表すものではないのでエラーになります。

だから、Option + l で継続行を挿入。その後に実際に Return キーで改行とする必要があります。二度手間です。むかしは、Option + Return が使えたのに...。

以降、この章はもう少し続くのだけど、あくまでこの章は全体的な概観なので詳しいことはそれぞれ別の章に記述されている。後々、重複してくることになるし、AppleScript 2.0 だからといって見るべきところはないので割愛。

Finder の alias list、使えるみたい

Mac OS X 10.4 の Finder(10.4 以前でも?)では、alias list が使えなかった。alias list は Finder で定義されているクラス。これを使うとファイルやフォルダの参照を Finder の参照ではなく、alias 参照のリストで返してくれる。

Script Editor で開く

tell application "Finder"
    set curSelection to selection as alias list
end tell

いちいち as alias と型変換をしなくていいので便利なのだけど、Mac OS X 10.4 では選択されている項目がひとつだけだとエラーになって動かなかった。また、複数を選択していても alias 参照に変換してくれなかった。だから、Finder の選択項目を処理する時はわざわざ次のように繰り返しで alias 参照に変換していた。

Script Editor で開く

tell application "Finder"
    set curSelection to selection

    if curSelection is {} then return

    set theseItems to {}
    repeat with thisItem in curSelection
        set end of theseItems to thisItem as alias
    end repeat

    theseItems
end tell

Mac OS X 10.5 でもこのバグの名残を見る事ができます。/Library/Scripts/Printing Scripts に入っている Convert to PDF.scpt などのスクリプトを開いて見ると、run ハンドラの最初の方で奇妙な事をしています。分かってて長いことほったらかしだったんですね(Convert to PDF.scpt の作成日が 2003 年なんですから)。

Mac OS X 10.5 の Finder では、alias list がようやく使えるようになりました。きちんと結果が返ってきます。でも、Mac OS X 10.4 が現役な事を考えると、上記のような配慮は必要なんでしょうね。まだまだ。

Safari と System Events と時々 Spaces

私はひねくれ者で、面白いと評判のいいものを旬の時期に手を出さない、流行ものは流行が終わってからこっそり手を出す。『東京タワー ~オカンとボクと、時々、オトン~』しかり。『博士の愛した数式』しかり。なかなか損な性格かもしれない。

いや、特に意味はないのだけど、表題を拝借したから...パクったのではありません。今、個人的に旬な話題なだけで。

閑話休題。Safari の話。バージョンが 3 になって、タブが操作できるようになりましたね。この変更に伴い、do javascript 命令はオプション in でタブを指定するように変更されています。以前まではどのドキュメントかを指定していました。以下のような感じです。

Script Editor で開く

tell application "Safari"
    if exists front document then return do JavaScript "getSelection()+''" in front document

    return ""
end tell

このスクリプトは、このままでも Safari 3 で動きます。そのうち動かなくなるのかな...と思わないでもないです。用語説明にあるように in オプションでタブを指定するなら以下のようになります。

Script Editor で開く

tell application "Safari"
    if exists front window then ¬
        return do JavaScript "getSelection()+''" in current tab of front window

    return ""
end tell

Safari の環境設定でタブを利用しない設定にしていても動きます。tab クラスは window クラスの要素なので、対象ウィンドウのどのタブかを指定する事になります。

さて。このスクリプト。実際には問題があり、必ずしも動くとは限らない。これは AppleScript 2.0 に限った話ではなく、Mac OS X になってからずっとなのですが、。front window や front document という参照が最前面のウィンドウやドキュメントを指さない事があるのです。そのため、最前面のウィンドウで選択文字列があるにも関わらず、結果が空の文字列、またはエラーになる事があります。この現象は、Cocoa アプリケーションにおいて顕著です。

かなり以前から分かっていた問題なのですが、ここで紹介しているスクリプトでは敢えてこの事を無視していました。が、Safari のバージョンアップで do javascript でウィンドウの指定が必要になるなら話は変わってきます。今まではたいていのアプリケーションで document を対象にしていたのでなんとかやりくりしていましたが...それもここまでのようです(実際のところ Xcode でもこの問題に悩まされ、爾来、避けて通るようになったのですが)。

AppleScript は一番前面にあるウィンドウが window 1(= front window)、その後ろに続く順に 2、3、4...と、順番のつけ方が決まっています。他にも every window や every file といった参照を行った時どのような順番でそれぞれの要素が返ってくるかという事も AppleScript では仕様の上で決まっています。基本的には上(手前)からと下(奥)へ。左から右への順番です。これらの順番の付け方は、リストで返ってくる結果に影響します。

front window(front document)と書いて一番後ろのウィンドウの参照が返ってくるのは気持ち悪いし、予期していない結果をもたらすので確実に最前面にあるウィンドウの参照を得たい。が、これは一筋縄ではいかない問題なのです。例えば、以下のようにして全てのウィンドウの参照を得たとします。

Script Editor で開く

tell application "Safari"
    every window
end tell

結果を見ると、表示されていないウィンドウ(環境設定やダウンロード)といったウィンドウの参照まで含まれます。また、Safari でダウンロードをしたときに開かれる「ダウンロード」というタイトルを持つウィンドウは、表示されていなくても front window といった参照で指定されてしまう事もあります。これが front document という参照なら、こういった関係のないウィンドウは除外される事になるのですが。

ちなみに window = document ではありません。document は window の要素で window に含まれるものです。window の中に document があるので every window は全て(表示されていないウィンドウも含む)のウィンドウの事で every document は document を持った全てのウィンドウという事になります。

つまり、document の指定は不要なウィンドウを対象に含まないのでそれだけ目的のウィンドウを指定するのが楽になるのです。全ての window の中から目的のウィンドウを指定するのより遥かに楽です。

少し、整理しましょう。やりたい事は、見えているウィンドウの重なり順で一番手前にあるドキュメントを持ったウィンドウを指定したい。問題は必ずしも一番手前にあるウィンドウが front window(or front document)の参照で取得できない。なぜ、ウィンドウの参照が欲しいのかというと、do javascript 命令でタブの指定を行う必要があるから。タブを指定するにはウィンドウの参照が必要で、かつ、そのウィンドウがドキュメントを持っていないといけない(一律に current tab of front window とすると「ダウンロード」などのウィンドウを指す可能性があり、結果、エラーにより終了する)。

さて、まずは document を持ったウィンドウのみを抽出してみましょう。window クラスは属性に document を持っています。これを利用し、フィルタ参照で抽出しましょう。

Script Editor で開く

tell application "Safari"
    windows whose document of it is not missing value
end tell

これでドキュメントを持ったウィンドウを取得できます(逆に document から window の参照を得る事はできません。これができるといいのですが)。取得できるのですが、どのウィンドウが最前面にあるのかは分かりません(分からないというより、結果が信頼できない)。また、このスクリプトが動くのは Mac OS X 10.5 Leopard の Safari 3 以降、AppleScript 2.0 以降だけかもしれませんし、どの Cocoa アプリケーションでも動く保証はありません。逆に言うと、これら以前の環境では document を持った window を調べる手段がないという事です。

なぜかというと、window クラスが持っている document 属性が何も返さないお飾りの属性になっている場合があるし、また、上記のようなフィルタ参照は動かなかったと思います(Mac OS X 10.4.11 で試してみました。やはり動きませんでした)。古い環境を考えなくていいなら上記の方法でいいのですが、ここでは Mac OS X 10.4 と Mac OS X 10.5 の互換性を考えて document の name 属性を利用します。

Script Editor で開く

tell application "Safari"
    set documentList to name of documents
end tell

これで、ドキュメントを持ったウィンドウの取得はできました。AppleScript を信頼するなら返ってきたリストの最初の要素が最前面のドキュメントになるのですが。そこで、このリストの中から最前面のウィンドウを調べるのですが...。

スクリプタブルな Cocoa アプリケーションにも関わらず、一番手前にあるウィンドウの参照を確実に返すアプリケーションがあります。また、このアプリケーションは windows と記述したときに表示されているウィンドウのみを対象にします。System Events です。

Script Editor で開く

tell application "System Events"
    tell process "Safari"
        name of windows
    end tell
end tell

このスクリプトを実行すると、表示されているウィンドウを重なり順のリストで返してくれます。リストの一番最初にある要素が表示されているウィンドウの中で最前面にあるウィンドウになります。先のドキュメントのリストと System Events で得られるウィンドウのリストを比較すれば、どれが一番最前面のウィンドウかが分かります。具体的には以下のような感じです。

Script Editor で開く

set frontWindow to getFrontWindow("Safari")
if frontWindow is missing value then return

name of frontWindow

on getFrontWindow(applicationID)
    tell application applicationID
        set documentList to name of documents
        if documentList is {} then return missing value
        set documentName to my windowFilter(name of it, documentList)
        if documentName is missing value then return missing value

        set frontWindow to windows whose name of it is documentName
        if frontWindow is {} then return missing value

        return first item of frontWindow
    end tell
end getFrontWindow

on windowFilter(thisProcess, compList)
    tell application "System Events"
        tell process thisProcess
            set windowList to name of windows
            repeat with thisWindow in windowList
                set thisWindow to contents of thisWindow
                if thisWindow is in compList then return thisWindow
            end repeat

            return missing value
        end tell
    end tell
end windowFilter

なんでこんな面倒な事せにゃならんねん、と思わないでもないですが。しかし、このスクリプトは動かない事もあります。Mac OS X 10.5 Leopard の目玉機能の一つ Spaces です。

Spaces で Script Editor と Safari を個別の画面に設定しているのですが、System Events で対象のアプリケーションプロセスを処理する時、そのプロセスが Spaces で別の画面にあると処理が行われないのです。

Script Editor で開く

tell application "System Events"
    tell process "Safari"
        windows
        --> {}
    end tell
end tell

このスクリプトを Script Editor で開き、Spaces で Safari を Script Editor と別の画面に割り当てると、Safari でウィンドウを開いているにも関わらず結果は必ず空になります。これだから System Events 嫌いなんだよ。UI Element を操作する時はプロセスを前面に持ってくる必要があるとか、制限が多過ぎて「確実」、「完璧」そして「美しく」、「華麗に」が好きな私に相容れないんだよ。

Script Editor で動作を確認しながら動かしている時はともかく、スクリプトメニューなどから実行すれば問題はないのですが。結果、Safari で最前面にあるウィンドウの現在のタブで選択している文字列を取得するスクリプトは以下のようになりました。

Script Editor で開く

on run
    try
        set frontWindow to getFrontWindow("Safari")
        if frontWindow is missing value then return

        set textSelection to getSelection(frontWindow)
    on error eMessage number eNumber
        tell application (path to frontmost application as text)
            activate
            display dialog (eNumber & " : " & eMessage) as text buttons {"OK"} default button 1 with icon 1
        end tell
    end try
end run

on getSelection(thisWindow)
    tell application "Safari"
        set selectedText to {}
        set end of selectedText to do JavaScript "getSelection();" in current tab of thisWindow

        set frameNum to do JavaScript "parent.frames.length;" in current tab of thisWindow
        set num to 0
        repeat while num < frameNum
            set theCommand to "parent.frames[" & num & "].getSelection();" as text
            set end of selectedText to do JavaScript theCommand in current tab of thisWindow
            set num to num + 1
        end repeat
        return selectedText as text
    end tell
end getSelection

on getFrontWindow(applicationID)
    tell application applicationID
        set documentList to name of documents
        if documentList is {} then return missing value
        set documentName to my windowFilter(name of it, documentList)
        if documentName is missing value then return missing value

        set frontWindow to windows whose name of it is documentName
        if frontWindow is {} then return missing value

        return first item of frontWindow
    end tell
end getFrontWindow

on windowFilter(thisProcess, compList)
    tell application "System Events"
        tell process thisProcess
            set windowList to name of windows
            repeat with thisWindow in windowList
                set thisWindow to contents of thisWindow
                if thisWindow is in compList then return thisWindow
            end repeat

            return missing value
        end tell
    end tell
end windowFilter

一応、フレームのあるサイトの選択文字列にも対応。以上、「Safari」と「System Events」と「Spaces」による三題噺でした。

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

まずは、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 は流行らないのかもしれませんね。