NSTask/NSPipe read from Unix command
cocoa, nspipe, nstask, objective-c
Solution
Here is a working example of how I usually do it:
task = [[NSTask alloc] init];
[task setLaunchPath:...];
NSArray *arguments;
arguments = ...;
[task setArguments:arguments];
NSPipe *outPipe;
outPipe = [NSPipe pipe];
[task setStandardOutput:outPipe];
outFile = [outPipe fileHandleForReading];
[outFile waitForDataInBackgroundAndNotify];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(commandNotification:)
name:NSFileHandleDataAvailableNotification
object:nil];
[task launch];
- (void)commandNotification:(NSNotification *)notification
{
NSData *data = nil;
while ((data = [self.outFile availableData]) && [data length]){
...
}
}
Problem
I am writing a Cocoa application which needs to execute a UNIX program and read its output, line by line, as they are produced. I set up a NSTask and NSPipe as such: ``` task = [[NSTask alloc] init]; pipe = [NSPipe pipe]; [task setStandardOutput:pipe]; //... later ... [task setArguments:...]; [task setLaunchPath:@"..."]; [task launch]; handle = [[task fileHandleForReading] retain]; ``` The command does not terminate until the program tells it to do so with `[task terminate]`. I have tried several methods of reading from the handle, such as `-readInBackgroundAndNotify`, `while([(data = [handle availableData]) length] > 0)`, and `-waitForDataInBackgroundAndNotify`, but the pipe never seems to yield any data. Is there some way I can "poke" the `NSTask` or `NSPipe` to flush the data through? EDIT: with `-readInBackgroundAndNotify`: ``` [handle readInBackgroundAndNotify]; notification_block_t handlerBlock = ^(NSNotification *notification) { NSData *data = [[notification userInfo] objectForKey: NSFileHandleNotificationDataItem]; /*... do stuff ...*/ [self addNotification: handle block: handlerBlock]; }; [self addNotification: handler block: handlerBlock]; //... - (void)addNotification:(id)handle block:(notification_block_t)block { [[NSNotificationCenter defaultCenter] addObserverForName: NSFileHandleReadCompletionNotification object: handle queue: [NSOperationQueue mainQueue] usingBlock: block]; } ``` with `-waitForDataInBackgroundAndNotify`: ``` [handle waitForDataInBackgroundAndNotify]; notification_block_t handlerBlock = ^(NSNotification *notification) { NSData *data = [handle availableData]; /*... do stuff ...*/ }; [self addNotification: handler block: handlerBlock]; ``` with `while` loop: ``` [self startProcessingThread: handle]; //... - (void)startProcessingThread:(NSFileHandle *)handle { [[NSOperationQueue mainQueue] addOperation: [[[NSInvocationOperation alloc] initWithTarget: self selector: @selector(dataLoop:) object: handle] autorelease]]; } - (void)dataLoop:(NSFileHandle *)handle { NSData *data; while([(data = [handle availableData]) length] > 0) { /*... do stuff ...*/ } } ``` EDIT 2: The arguments are set as follows (the command is `tshark`): ``` NSArray *cmd = [NSArray arrayWithObjects:@"-R", @"http.request", @"-Tfields", @"-Eseparator='|'", @"-ehttp.host", @"-ehttp.request.method", @"-ehttp.request.uri", nil]; cmd = [[cmd arrayByAddingObjectsFromArray:[self.ports map:^(id arg1, NSUInteger idx) { return [NSString stringWithFormat:@"-d tcp.port==%d,http", [arg1 intValue]]; }]] arrayByAddingObject:[@"dst " stringByAppendingString: [self.hosts componentsJoinedByString:@" or dst "]]]; [self.tsharktask setArguments:cmd]; ```