対象の触り方

GUI環境における操作の基本は、操作対象を選択して、命令することである。すべての操作は「何を、どうする」という目的語と述語の関係で表現できる。(もちろん、操作しているのはユーザー自身なのだから、主語はユーザーである。でも、ここではあまり重要でない)

GUIの操作は初めての人にも分かりやすいのだけど、慣れてくるとその冗長な操作が面倒臭くなってくる。そこでキーボードショートカット、という仕組みが用意された。command-Cでコピーとか、command-Wでウィンドウを閉じるとか、キーの組み合わせでGUIを操作する仕組みだ。

たぶん、慣れている人ほどショートカットを多用して、素早い操作を実現していると思う。パソコンはキーボードとコマンドラインで操作するCUIから、マウスとグラフィックで操作するGUIに進化したけど、より効率を求めるとCUIに回帰しているという面白い現象だ。

さらに効率を求めると、スクリプト言語を使い始める。退屈な繰り返し処理をまとめて実行したり、複雑で間違い易い処理を正確に実行したり。

ところで、CUI環境とスクリプト言語は、最高に相性がいい。どちらもCUI環境なので、「何を」の情報の伝達が直感的でスムーズなのだ。

  • 基本的にファイルパス、あるいはテキストデータを指定することが多い。
  • たまに、キー入力、あるいはクリップボードを指定することもあるかも。

一方、GUI環境とスクリプト言語は、GUI環境で狙った対象「何を」の部分をスクリプト言語で表現するのに苦労することが多い。「どうする」の部分の処理方法は明確に分かっているのに、「何を」の部分の表現の仕方に試行錯誤して、時間を取られてしまうのだ...。

操作対象をどうやって取得するのか、その方法をいくつか探ってみた。

Finderで選択中のファイルを対象にする

よくありがちな状況としては、Finderで選択中のファイルに対して何らかの処理を実行すること。例えば、radikoの.flvファイルをHE-AACに変換してiTunesに登録するとか。

AppleScriptの場合
  • Finderの選択項目はselectionで取得できる。
  • その内容は、Finder file参照のリストになっている。
  • 一つだけ選択・複数選択に関係なく、常にリストになっている。
  • repeatでリストの中のFinder file参照を一つずつ取り出して処理している。


tell application "Finder" to set select_list to selection
--結果:{document file "test_path_to.me.scpt" of folder "Desktop" of folder "zari" of folder "Users" of startup disk of application "Finder"}

repeat with f in select_list
f --Finder file参照リストの1番目のアイテム
--結果:item 1 of {document file "text_alias_posix_file変換.scpt" of folder "_Development" of folder "Scripts" of folder "Library" of folder "zari" of folder "Users" of startup disk of application "Finder"}
f as alias
--結果:alias "Macintosh HD:Users:zari:Library:Scripts:_Development:text_alias_posix_file変換.scpt"
(f as alias)'s POSIX path
(f as text)'s POSIX path
--結果:"/Users/zari/Desktop/abcd.txt"
end repeat

  • Finderで選択中のファイルに対して操作する場合、たぶん上記のような書き方が雛形になるのだと思う。
  • この雛形に対して、的確なファイル参照で、欲しい情報を取り出して、目的の処理を実行することになると思う。
  • 雛形だけでは分かりにくいので、実際のサンプルとして、flvからmp4を劣化なしで取り出すスクリプトを考えてみた。
  • 結局の所、核心の処理はffmpegを利用しており、選択中のファイル情報から適切に処理する引数を渡すラッパーなのである。


property ffmpeg_path : "/opt/local/bin/ffmpeg"
property growlnotify_path : "/usr/local/bin/growlnotify"

tell application "Finder" to set selection_list to selection
repeat with f in selection_list
set file_path to (f as text)'s POSIX path
set non_ext_path to file_path's items 1 thru -5 as text
set in_path to file_path's quoted form
set out_path to (non_ext_path & ".mp4")'s quoted form
set ext to file_path's items -4 thru -1 as text
if ext = ".flv" then
do shell script ffmpeg_path & " -y -i " & in_path & " -vcodec copy -acodec copy " & out_path
do shell script growlnotify_path & " -m " & in_path & " '完了'"
end if
end repeat

Automatorの場合(AppleScript連携)
  • Automatorのサービスを使っても、Finderで選択中のファイルへ簡単に取得できる。
  • サービスの利点は、右クリックしてコンテクストメニューからアクセスできることである。
  • サービスで受け取る「ファイルまたはフォルダ」は、「選択されたFinder項目を取得」アクションと同等だ。

  • 実行してみると、input変数には、選択中のファイルへのalias参照のリストが渡された。
  • flvからmp4を取り出すサンプルは、以下のようになった。

  • ほとんど同じで、変更点は以下の部分のみ。
    • tell application "Finder" to set selection_list to selection は削除。
    • 変数selection_listは、inputに変更。
  • これで、右クリックして、コンテクストメニューに表示されるようになった。


Automatorの場合(シェルスクリプト連携)
  • Automatorは柔軟で、シェルスクリプトと連携して実行することもできる。
  • flvからmp4を取り出すサンプルは、以下のようになった。

for f in "$@"
do
	if [ "${f##*.}" = "flv" ]; then
		/opt/local/bin/ffmpeg -y -i "$f" -vcodec copy -acodec copy "${f%.*}.mp4"
		/usr/local/bin/growlnotify -m "$f" '完了'
	fi
done
  • 自然言語風を狙ったAppleScriptは、弱点である冗長さ丸出し。
  • 対して、シェルスクリプトのこれ以上ない簡潔さは気持ちいい。
  • 変数展開のルールを知らないと、まるで未知の暗号なのだが、分かってしまえば寸分の無駄もない文法に美しさも感じる。可読性も良い。

iTunesiPhotoで選択中のアイテムを対象にする

Finderで選択中のアイテムの扱い方は、だいぶ理解できた。それでは、iTunesで選択中のファイルを操作するにはどうすべきか?

AppleScript
  • iTunesの場合は簡単で、selectionをselection's locationにするだけで、曲ファイルへのエイリアス参照リストが取得できた。


tell application "iTunes" to set selection_list to selection
--結果:{file track id 11893 of user playlist id 11889 of source id 65 of application "iTunes", file track id 11894 of user playlist id 11889 of source id 65 of application "iTunes"}

tell application "iTunes" to set selection_list to selection's location
--結果:{alias "Macintosh HD:Users:zari:Music:iTunes:iTunes Music:Compilations:BOSSANIMATION:13 やつらの足音のバラード.m4p", alias "Macintosh HD:Users:zari:Music:iTunes:iTunes Music:熊木杏里:君の名前 - EP:01 君の名前.m4p"}

  • iPhotoの場合もselection's locationとやりたくなるが、それではダメだった...。(locationという属性さえ、iPhotoには登録されていない)
  • 調べてみると、image path・thumbnail pathがパスを取得する属性になりそうだ。
  • 早速、selection's image pathとやってみるが、やはりエラー発生。(変換できません、と)
  • 面倒だけど、地道にリストの中身を一つずつ取り出して処理する必要がありそうだ。
  • image path・thumbnail pathの結果は、UNIX形式のパステキストとして返される。(alias参照ではないので注意が必要)


tell application "iPhoto"
set image_path_list to {} set thumbnail_path_list to {} --repeat with i in selection --selectionではエラーになる
repeat with i in selection as list --selection as list とする必要あり
set image_path_list's end to i's image path
set thumbnail_path_list's end to i's thumbnail path
end repeat
end tell

image_path_list
--結果:{"/Users/zari/Pictures/iPhoto Library/Originals/2010/2010:09:18/DSC_0276.JPG", "/Users/zari/Pictures/iPhoto Library/Modified/2010/2010:09:18/DSC_0277.JPG"}

thumbnail_path_list
--結果:{"/Users/zari/Pictures/iPhoto Library/Data/2010/2010:09:18/DSC_0276.jpg", "/Users/zari/Pictures/iPhoto Library/Data/2010/2010:09:18/DSC_0277.jpg"}

Automatorの場合(AppleScript連携)
  • サービスからファイルパスを取得できそうな錯覚に陥ってしまうのだが、実はできなかった。
  • サービスからファイルパスを取得できるのは、Finderを利用している時だけのようだ。
  • ではどうするか、というと「選択されたiTunesトラックを取得」アクションで取得した。
  • しかし、「選択されたiTunesトラックを取得」した値は、iTunesのオブジェクトリスト。
{file track id 11893 of user playlist id 11889 of source id 65 of application "iTunes", file track id 11894 of user playlist id 11889 of source id 65 of application "iTunes"}
  • そこで、「指定されたFinder項目を取得」を繋げてみると、UNIXパス形式のリストに変換された。
(
    "/Users/zari/Music/iTunes/iTunes Music/Compilations/BOSSANIMATION/13 \U3084\U3064\U3089\U306e\U8db3\U97f3\U306e\U30cf\U3099\U30e9\U30fc\U30c8\U3099.m4p",
    "/Users/zari/Music/iTunes/iTunes Music/\U718a\U6728\U674f\U91cc/\U541b\U306e\U540d\U524d - EP/01 \U541b\U306e\U540d\U524d.m4p"
)
  • その後、「AppleScriptを実行」を繋げると、alias参照形式のリストに変換された値を取得できた。
{alias "Macintosh HD:Users:zari:Music:iTunes:iTunes Music:Compilations:BOSSANIMATION:13 やつらの足音のバラード.m4p", alias "Macintosh HD:Users:zari:Music:iTunes:iTunes Music:熊木杏里:君の名前 - EP:01 君の名前.m4p"}


  • 上記はiTunesの場合だが、iPhotoの場合も全く同じ手順でalias参照のリストを取得することが出来た。
  • 同じ基準で常にalias参照が取得できるのでとても便利だ。
  • AppleScriptだと、アプリケーションごとに方法が違ってしまい、その都度調べる必要がある。
Automatorの場合(シェルスクリプト連携)

(
    "/Users/zari/Music/iTunes/iTunes Music/Compilations/BOSSANIMATION/13 \U3084\U3064\U3089\U306e\U8db3\U97f3\U306e\U30cf\U3099\U30e9\U30fc\U30c8\U3099.m4p",
    "/Users/zari/Music/iTunes/iTunes Music/\U718a\U6728\U674f\U91cc/\U541b\U306e\U540d\U524d - EP/01 \U541b\U306e\U540d\U524d.m4p"
)

選択中のテキストを対象にする

選択中のファイルと同じように、選択中のテキストに対しても、様々な処理を実行できると便利である。

AppleScript
  • アクティブなアプリケーションの選択中のテキストを取得するには、ちょっと工夫が必要かもしれない。
  • 自分の場合は、GUIスクリプティングでcommand-Cを実行して、クリップボードの内容を取得してしる。


tell application "System Events"
keystroke "c" using command down
delay 0.5
the clipboard as text
end tell

  • delay 0.5 は、コピー操作が完了するまでの待機時間。
  • 待機時間がないと、クリップボードへデータが転送される前に次に進んでしまうことがあり、予想外の結果に混乱する。
  • 上記スクリプトにほんのちょっと追加するだけで、選択中のテキストをシングルクォートで囲うスクリプトが出来上がる。


tell application "System Events"
keystroke "c" using command down
delay 0.5
set the clipboard to "'" & (the clipboard as text) & "'"
keystroke "v" using command down
end tell

  • 選択中のテキストを囲うという需要は結構あるので、上記以外にもカッコやタグなどを用意しておくと、小さな幸せを感じるかもしれない。
Automatorの場合(AppleScript連携)
  • Automatorのサービスも、選択中のテキスト操作はかなり得意な分野である。
  • シングルクォートで囲うサンプルは、核心部分のコードだけになってしまった。

  • しかも、右クリックして、コンテクストメニューから辿れるので、操作も簡単。
Automatorの場合(シェルスクリプト連携)
  • シングルクォートで囲うサンプルは、やはり核心部分だけのコードだけ。

  • ところで、シェルスクリプトのテキスト操作コマンドはかなり強力に揃っている。
  • 囲うだけの処理に終わらせるのは勿体ない。
  • 「ワークフロー実行時にこのアクションを表示する」設定にして、以下のようにしてみた。

  • テキストを選択して、実行してみると...

  • アクションが表示されて、編集してから実行できるようになるのだ。
  • 例えば、sedコマンドの良く使いそうなオプション設定をいくつか用意しておくと、便利かもしれない。
  • もちろん、全く新規にコマンドを書き直してしまっても良し、パイプで連携させても良し。


的確に選択対象を取得することは、幸せなスクリプト環境に通じるのであった。

参考ページ

シェルスクリプトのことで分からない時に、いつも頼ってしまうのが以下のページ。(感謝です!)