Actorclavilis Actorclavilis - 2 months ago 16
Objective-C Question

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

Answer

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