macのデスクトップアプリ(Mountain Lionより新しいもの)ではfprintf(stdout, ...)
やfprintf(stderr, ...)
が捨てられているようで、これを確認する手段を調べた。
パターン1: ターミナルから起動する
〜.app
というアプリケーションの実態(実行ファイル)は /path/to/〜.app/Contents/MacOS/〜
なので、
これをターミナルから実行することでターミナルにstdoutやstderrを表示することができる。
通常のコマンドと同様パイプで別のコマンドに渡したり、リダイレクトでファイルに出力することも可能。
パターン2: NSLogにパイプしてコンソールで確認する
ターミナルはちょっと、という状況だったため、実行ファイルを起動して、stdout/stderrをNSLogにパイプするmacアプリを作ってみた。
NSLogの類であればコンソール (/Applications/Utilities/Console.app)で確認できる。
「stdout、stderrを見たいアプリ(〜.app)」を作ったアプリのアイコンにドラッグ&ドロップするとそのアプリが起動して、stdout/stderrがNSLogで出力される。
実装の説明(簡易)
実装した内容はAppDelegate.mとInfo.plistの修正、Sandboxの無効化だけ。
Info.plistの修正
アプリアイコンにドラッグ&ドロップするために対応する拡張子を指定する必要がある。
今回は.app
を追加。
<dict> <key>CFBundleTypeName</key> <string>Application</string> <key>LSHandlerRank</key> <string>Default</string> <key>CFBundleTypeExtensions</key> <array> <string>app</string> </array> </dict>
Xcodeで見るとこんな感じ。
Sandboxの無効化
プロジェクトの設定のCapabilitiesからApp SandboxをOFFに変更。 これがONだとドロップしたアプリも一緒にSandbox内で起動してしまうため、普段利用している設定等が読み込めなかった。
AppDelegate.mの実装
ドラッグ&ドロップ時の処理 (
application:openFile:
)ファイルをアプリケーションアイコンにドロップするとapplication:openFile:が呼ばれる。
ファイルパスが引数に渡されるので、
/path/to/〜.app
から/path/to/〜.app/Contents/MacOS/〜
を生成してNSTaskで実行する([self launchAppWithPath:apppath]
)。
- (BOOL)application:(NSApplication *)sender openFile:(NSString *)filename { // get Application path // cf. https://developer.apple.com/documentation/foundation/nsregularexpression NSError *error = NULL; NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"/([^/]+).app" options:NSRegularExpressionCaseInsensitive error:&error]; NSArray *matches = [regex matchesInString:filename options:0 range:NSMakeRange(0, [filename length])]; if ([matches count] < 1) { return NO; } NSTextCheckingResult *match = [matches objectAtIndex:0]; NSRange firstHalfRange = [match rangeAtIndex:1]; NSString *appname = [filename substringWithRange:firstHalfRange]; NSString *apppath = [NSString stringWithFormat:@"%@/Contents/MacOS/%@", filename, appname]; NSLog(@"Launching %@ (%@)", appname, apppath); [self launchAppWithPath:apppath]; return YES; }
アプリの実行とstdout、stderrのパイプ (
launchAppWithPath:
,receivedData:
,receivedErrData:
)NSTaskで起動する実行ファイルを指定し、NSPipe (変数pとep)をタスクのstdout, stderrに接続している。
NSFileHandleにてパイプに何か出力があった時の通知を有効にして(waitForDataInBackgroundAndNotify
)
その通知発生時の処理としてreceivedData:
、receivedErrData:
メソッドを指定している。
- (void)launchAppWithPath:(NSString *)apppath { NSTask *task = [[NSTask alloc] init]; [task setLaunchPath: apppath]; // pipe stdout to NSLog // cf. https://stackoverflow.com/a/6931865/514411 NSPipe *p = [NSPipe pipe]; [task setStandardOutput:p]; NSFileHandle *fh = [p fileHandleForReading]; [fh waitForDataInBackgroundAndNotify]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedData:) name:NSFileHandleDataAvailableNotification object:fh]; // pipe stderr to NSLog NSPipe *ep = [NSPipe pipe]; [task setStandardError:ep]; NSFileHandle *efh = [ep fileHandleForReading]; [efh waitForDataInBackgroundAndNotify]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(receivedErrData:) name:NSFileHandleDataAvailableNotification object:efh]; [task launch]; }
receivedData:
、receivedErrData:
はほぼ同じで、通知内容からデータを文字列として読み取り、NSLogで出力している。
receivedData:
とreceivedErrData:
の違いは出力時のprefixがstdout:
かstderr:
か、のみです。
// pipe stdout to NSLog // cf. https://stackoverflow.com/a/6931865/514411 - (void)receivedData:(NSNotification *)notif { NSFileHandle *fh = [notif object]; NSData *data = [fh availableData]; if (data.length > 0) { // if data is found, re-register for more data (and print) [fh waitForDataInBackgroundAndNotify]; NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"stdout: %@" ,str); } }
参考URL
- osx - Dropping promised files on to application icon in Dock - Stack Overflow
- iconにドロップした時の挙動
- application:openFile: - NSApplicationDelegate | Apple Developer Documentation
- application:openFile:のドキュメント
- Core Foundation Keys
- Info.plistの設定値
- NSRegularExpression - Foundation | Apple Developer Documentation
- 正規表現でマッチした部分(アプリ名)の取得
- osx - NSTask's real-time output - Stack Overflow
- 外部実行ファイルの実行と出力のリアルタイム化