objective-ccocoanstasknspipe

NSTask/NSPipe read from Unix command


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];

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]){
            ...
        }   
    }