Cocoaはやっぱり!
Perlスクリプトを実行する
CocoaからUNIX
今回のテーマ

Cocoaには、UNIXのコマンドを呼び出す仕組みが備わっています。ということは、UNIXの世界の様々なソフトを利用することが可能です。Mac OS Xにも沢山のUNIXコマンドがありますし、Perlも標準でインストールされています。

ということは、もうPerlはOSの一部の機能と考えてCocoaの中から利用しても問題ありません。Perlの強力な文字列処理能力や豊富なスクリプトをCocoaから利用しない手はありません。最近のマシンは高速ですし、アプリケーションの一部をPerlで処理してもらうというのも選択肢の一つとして考えても悪くないはずです。

そこで今回は、Cocoaアプリケーションから、UNIXコマンドやPerlスクリプトを実行する方法を解説します。

改版履歴

UNIXコマンドの呼び出し方

MacOS Xでは、パッケージという概念が導入されました。これは、Finder上ではふつうのアプリケーションのファイルのように見えるけど、実体はフォルダーで、そのフォルダーの中にアプリケーションを構成するファイル郡を閉じ込めておくものです。これを利用すると、パッケージの中にPerlスクリプトを入れておくことが出来ます。つまり、一見アプリケーションなんだけど、中ではPerlが動いているということができるわけです。

さて、Perlを動かすといっても実際はUNIXのコマンドを実行するだけで、NSTaskというクラスを使うことで実現できます。NSTaskの生成は以下のようにします。

NSTaskのインスタンス生成
NSTask *task = [ [ NSTask alloc ] init ];

生成したら実行したいコマンドのパスをsetLaunchPathで指定します。Perlは、「 /usr/bin/perl 」にあります。

NSTask : 実行するコマンドのパスを指定
書式 : - (void) setLaunchPath : (NSString *) path 入力 : path - 実行するコマンドのパス

コマンドへパラメータを指定するには、setArguments : を使います。文字列の配列を渡します。配列の最初がPerlスクリプトのパスで、それ以降はPerlスクリプトに渡すパラメータになります。

NSTask : 実行するコマンドへのパラメータ指定
書式 : - (void) setArguments : (NSArray *) arguments 入力 : arguments - 引き数を文字列の配列で指定

実行時のカレントディレクトリを指定するには、setCurrentDirectoryPathを指定します。Perlスクリプトでファイルの入出力がある場合で、相対パスを使っている場合は、ここが起点になります。

NSTask : 実行時のカレントディレクトリの指定
書式 : - (void) setCurrentDirectoryPath : (NSString *) path 入力 : path - 実行時のカレントディレクトリ

ついに実行です。launchを呼びます。コマンドを起動したらすぐに戻ってきます。

NSTask : 実行する
書式 : - (void) launch

実行したコマンドが終わるのを待つには、waitUntilExitを使います。

NSTask : 実行終了を待つ
書式 : - (void) waitUntilExit

実行中に別のことをしたい場合もありますので、そういう場合は待たずにisRunningを使って実行中かどうかを取得しましょう。

NSTask : 実行中かどうか
書式 : - (BOOL) isRunning 出力 : RETURN : YES(=実行中)、NO(=実行終了)

コマンドが終了した時のステータスを得るにはterminationStatusを使います。一般的には0だと正常終了です。コマンドの仕様に依存しますので、コマンドの仕様を参照してください。

NSTask : 実行結果のステータス
書式 : - (int) terminationStatus 出力 : RETURN : コマンドの仕様に依存しますが、通常は0が正常終了

実行結果のステータスではintの値しか取れません。Perlから文字列を受け取りたいこともあります。この場合は、ファイルを経由してもいいですが、標準入出力を使っても出来ます。今回は、標準エラー出力を使って説明します。

NSTaskの標準エラーをどこに出力させるかというのをsetStandardError : で指定することが出来て、出力先としてNSpipeというパイプのクラスが用意されています。NSPipeは、以下のように生成します。

NSPipeのインスタンス生成
NSPipe *pipeErr = [ NSPipe pipe ];
NSPipe : Pipeの生成
書式 : + (id) pipe 出力 : RETURN : 生成したNSPipe。autoreleaseが呼ばれていることに注意。

標準出力先にはNSPipeとNSFileHandleが指定できます。NSFileHandleにするとファイルに出力されますので、今回は使用しません。

NSTask : 標準エラーの出力先指定
書式 : - (void) setStandardError : (id) file 入力 : file : 出力先。NSFileHandleかNSPipeが使用できる。

NSPipeに出力された内容を取得するには、fileHandleForReadingを使います。これによってNSFileHandleが返ってきますので、ファイルを読む要領でNSPipeからデータを取り出します。読み出しは、availableDataが一番簡単です。

NSPipe : パイプを読み出すためのNSFileHandleを返す
書式 : - (NSFileHandle *) fileHandleForReading 出力 : RETURN : NSPipeが読みだせるNSFileHandle。
NSFileHandle : ファイルの読み出し
書式 : - (NSData *) availableData 出力 : RETURN : 現在取りだせるデータ。

とりあえずここまででコードを組んでみると以下のようになります。

NSPipeのインスタンス生成
int callPerl(void) { NSTask * task = [ [ NSTask alloc ] init ]; NSPipe * pipeErr = [ NSPipe pipe ]; NSMutableString* curPath = @" お好みで "; // カレントパス NSMutableString* scrPath = @" お好みで "; // スクリプトのパス // 標準エラー出力の指定 [ task setStandardError : pipeErr ]; // 実行 [ task setLaunchPath : @"/usr/bin/perl" ]; [ task setCurrentDirectoryPath : curPath ]; [ task setArguments : [ NSArray arrayWithObject : scrPath ] ]; [ task launch ]; [ task waitUntilExit ]; { // 標準エラーの読み出し NSData* dataErr = [ [ pipeErr fileHandleForReading ] availableData ]; NSString* strErr = [ NSString stringWithFormat : @"%s", [ dataErr bytes ] ]; NSLog( strErr ); } return( [ task terminationStatus ] ); }
パッケージ内のPerlスクリプトの実行

で、目標はパッケージの中にあるPerlスクリプトを実行することです。ってことは、そのファイルパスを取得しないといけません。それには、NSBundleを使用します。NSBundleはパッケージ内のファイルにアクセスしたりするためのクラスです。

NSBundleの詳細は説明すると長くなるので、ここでは割愛して、パスを取得する手順を説明します。メインバンドルを取得して、そのパスを取得するとアプリケーションパッケージのフォルダーのパスが得られます

例えば、「 /Application/CallPerl.app 」みたいになります。

NSBundle : メインバンドルの取得
書式 : + (NSBundle *) mainBundle 出力 : RETURN : メインバンドル。
NSBundle : バンドルのパスを取得
書式 : - (NSString *) bundlePath 出力 : RETURN : バンドルのパス。

パッケージの中にPerlのスクリプトを入れたいので、ProjectのResourcesのところにスクリプトをドロップして登録します。そうすると、ビルドした時に、パッケージの中の「 Contents/Resources/ 」の中にコピーされます。ということは、以下のコードでパスが取得できます。スクリプトのファイル名は「 main.pl 」とします。

Perlスクリプトのパスの取得
NSMutableString* scrPath = [ NSMutableString string ]; [ scrPath setString : [ [ NSBundle mainBundle ] bundlePath ] ]; [ scrPath appendString : @"/Contents/Resources/main.pl" ];

カレントディレクトリの設定は、用途によって変わると思いますが、アプリケーションと同じフォルダーにログを残したいことが多いかと思って以下のようにしました。メインバンドルのパスからアプリケーション名を除去しています。

カレントディレクトリの設定
NSMutableString* curPath = [ NSMutableString string ]; [ curPath setString : [ [ NSBundle mainBundle ] bundlePath ] ]; [ curPath setString : [ curPath stringByDeletingLastPathComponent ] ];
バンドル内のPerlスクリプトの実行
#import <Cocoa/Cocoa.h> int callPerl(void) { NSTask * task = [ [ NSTask alloc ] init ]; NSPipe * pipeErr = [ NSPipe pipe ]; NSMutableString* curPath = [ NSMutableString string ]; NSMutableString* scrPath = [ NSMutableString string ]; // 標準エラー出力の指定 [ task setStandardError : pipeErr ]; // パスの取得 [ curPath setString : [ [ NSBundle mainBundle ] bundlePath ] ]; [ curPath setString : [ curPath stringByDeletingLastPathComponent ] ]; [ scrPath setString : [ [ NSBundle mainBundle ] bundlePath ] ]; [ scrPath appendString : @"/Contents/Resources/main.pl" ]; // 実行 [ task setLaunchPath : @"/usr/bin/perl" ]; [ task setCurrentDirectoryPath : curPath ]; [ task setArguments : [ NSArray arrayWithObject : scrPath ] ]; [ task launch ]; [ task waitUntilExit ]; { // 標準エラーの読み出し NSData* dataErr = [ [ pipeErr fileHandleForReading ] availableData ]; NSString* strErr = [ NSString stringWithFormat : @"%s", [ dataErr bytes ] ]; NSLog( strErr ); } return( [ task terminationStatus ] ); } int main( int argc, const char *argv[] ) { NSAutoreleasePool* arp = [ [ NSAutoreleasePool alloc ] init ]; int iStatus; iStatus = callPerl(); if ( iStatus == 0 ) NSLog( @"Success" ); else NSLog( @"Fail" ); [ arp release ]; return( iStatus ); }