CocoaDev

Edit AllPages

An object-oriented interface for accessing pipes usually used with NSTask to redirect task output to a Cocoa UI element.

http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSPipe_Class/index.html

A “pipe” is a communications channel that links one process to another. Data written on one of the pipe by one program can be read by a second program. Pipes are unidirectional. To pass data back and forth between two programs, you’d use two pipes. Whenever you link commands in the shell (like “ls -lat head”), you’re using a pipe under the hood to move data around.

“The data that passes through the pipe is buffered; the size of the buffer is determined by the underlying operating system.”

from: http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSPipe_Class/index.html

The NSPipe buffer limit seems to be 4096 bytes (cf. /usr/include/limits.h: “… #define _POSIX_ARG_MAX 4096 …”)

And also see this related issue: Max arguments for NSTask? ( http://www.cocoabuilder.com/archive/message/cocoa/2002/10/27/66713 )

Here’s an Xcode Foundation Tool example:

int main (int argc, char *argv[]) { NSAutoreleasePool *pool = [NSAutoreleasePool new];

NSString *file = @"/path/to/input/file";   
 
NSData *data1 = [NSData dataWithContentsOfFile:file]; 
 
printf("data1 bytes: %i\n", [data1 length]);    // should be > 4096 
 
//NSRange range = {0, [data1 length]}; 
//NSRange range = {0, 4097};  
NSRange range = {0, 4096};
 
NSData *data2 = [data1 subdataWithRange:range]; 

printf("data2 bytes: %i\n", [data2 length]);   
 
NSPipe *inPipe = [NSPipe pipe];
NSFileHandle *fh = [inPipe fileHandleForWriting];  
[fh writeData: data2];  
[fh closeFile];
 
NSPipe *outPipe = [NSPipe pipe];
 
NSTask *task = [NSTask alloc] init] autorelease];
[task setStandardInput:inPipe];   
[task setStandardOutput:outPipe];
[task setLaunchPath:@"/bin/cat"]; 
NSArray *args = [NSArray arrayWithObjects:@"-n", nil];   
[task setArguments:args];
[task launch];                                                                     
 
NSData *output = [[outPipe fileHandleForReading] readDataToEndOfFile];
NSString *string = [[[NSString alloc] initWithData:output encoding:NSUTF8StringEncoding] autorelease];
 
NSLog(@"\n%@\n", string);
 
[pool drain];
return 0; }

</source>


Another use for [[NSPipe is to capture data from a method which requires a file handle (FILE*) into an NSData object. This is useful for interacting with a lot of pesky non-objective-c libraries:

+(NSData)captureFileStreamIntoDataObject { NSPipe thePipe = NSPipe alloc] init];
[NSThread detachNewThreadSelector:@selector(backgroundThread:) toTarget:self withObject:[thePipe fileHandleForWriting;
NSData* theData = nil; NSMutableData* theMutableData = [NSMutableData data]; while((theData = thePipe fileHandleForReading] availableData]) && [theData length]) { [theMutableData appendData:theData]; } [thePipe release]; return theMutableData; }

+(void)backgroundThread:(NSFileHandle)theFileHandle { NSAutoreleasePool pool = [[NSAutoreleasePool alloc] init]; FILE* theFilePointer = fdopen([theFileHandle fileDescriptor], “w”);

commandWhichWritesToFile(theFilePointer);

fclose(theFilePointer); [theFileHandle closeFile]; [pool release]; }

</source>


/*

Sample code showing how to write more than 64 KB to the stdin of an NSTask using NSPipe & pthread.

this command will be rewritten in Objective-C using an NSTask to execute sh -c ‘…’

cat testfile.txt | sh -c ‘cat -n | tail -n 1’

compile with:

gcc -Wall -O3 -x objective-c -fobjc-exceptions -framework Foundation -lpthread -o nspipe_pthread_writedata nspipe_pthread_writedata.m

./nspipe_pthread_writedata

create testfile.txt

jot -b “line” 10000000 > testfile.txt # 48M #jot -b “line” 50000000 > testfile.txt # 238M echo ‘Hello, world!’ » testfile.txt stat -f “%z bytes” testfile.txt du -h testfile.txt

References:

Todo:

*/

// This sample code is based on a code snippet by Dave Keck, http://pastie.org/911738.

#import <Foundation/Foundation.h> #import #import

static NSMutableString *outString = nil;

void *threadFunction(NSPipe *pipe) {

NSAutoreleasePool alloc] init];

NSPipe *outPipe = [[NSPipe alloc] init]; NSTask *task = [[NSTask alloc] init];

NSString *cmdstring = @”/bin/cat -n | /usr/bin/tail -n 1”; NSArray *args = [[NSArray alloc] initWithObjects: @”-c”, cmdstring, nil];

NSData *output = nil; NSString *str = nil;

[task setStandardInput: pipe];
[task setStandardOutput: outPipe]; [task setLaunchPath: @”/bin/sh”]; [task setArguments: args];

[task launch];

outString = [[NSMutableString alloc] init];

while ( (output = [[outPipe fileHandleForReading] availableData]) && [output length] ) { str = [[NSString alloc] initWithFormat:@”%.*s”, [output length], [output bytes; [outString appendString: str]; [str release]; }

[task release]; [args release]; [outPipe release];

pthread_exit(outString);

}

int main(int argc, const char *argv[]) {

NSAutoreleasePool alloc] init];

NSString *file = @”testfile.txt”;

void *outputString = nil;

NSData *data = [[NSData alloc] initWithContentsOfFile: file];

printf(“file size: %i bytes\n”, [data length]);

NSPipe *pipe = [[NSPipe alloc] init];

pthread_t thread = nil; if (pthread_create(&thread, nil, (void* ()(void))threadFunction, pipe) != 0) { perror(“pthread_create failed”); return 1; }

[[pipe fileHandleForWriting] writeData: data]; [[pipe fileHandleForWriting] closeFile];

// wait until thread has finished execution if (pthread_join(thread, &outputString) != 0) { perror(“pthread_join failed”); return 1; }

//NSLog(@”\n%@”, [outputString class]);

NSString *newstr = [[NSString alloc] initWithString: outputString]; NSLog(@”\n%@”, newstr);

[data release]; [pipe release]; [newstr release]; [outString release];

return 0;

}

</source>


/*

ipctest.c by Dave Keck

compile with:

gcc -Wall -O3 -x objective-c -fobjc-exceptions -framework Foundation -o ipctest ipctest.c

./ipctest

Source: “NSPipe (NSFileHandle) writedata limit?” (April 09, 2010), http://www.cocoabuilder.com/archive/cocoa/284765-nspipe-nsfilehandle-writedata-limit.html, http://themha.com/ipctest (by Dave Keck)

*/

#import <Foundation/Foundation.h> #import #import #import #import

#define DATA_LENGTH 0x100000 //#define DATA_LENGTH 0x10000000

int main(int argc, const char *argv[]) {

NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];

if (argc == 1)
{

    /* Parent */
    
    NSPipe *pipe = nil;
    pid_t pid = 0;
    const char *readDescriptorString = nil;
    
    pipe = [NSPipe pipe];
    readDescriptorString = [[NSString stringWithFormat: @"%d", [[pipe fileHandleForReading] fileDescriptor UTF8String];
    fprintf(stdout, "readDescriptorString: %s\n", readDescriptorString);

    pid = fork();
    
        assert(pid >= 0);
    
    if (!pid)
    {
        /* Child */
        
        execl(argv[0], argv[0], readDescriptorString, (char *)NULL);
        //execl(argv[0], argv[0], readDescriptorString, nil);
        _exit(0);
    }
    
    /* Parent (continued) */
    
    void *primitiveData = malloc(DATA_LENGTH);
    NSData *data = [NSData dataWithBytes: primitiveData length: DATA_LENGTH];
    uint32_t dataLength = DATA_LENGTH;
    
    // Assuming that both processes share same endianness (bad idea)
    pipe fileHandleForWriting] writeData: [NSData dataWithBytes: &dataLength length: sizeof(dataLength);
    pipe fileHandleForWriting] writeData: data];
    
    NSLog(@"Parent: finished writing data, sleeping it off...");
    
    for (;;)
        sleep(-1);

}

else if (argc == 2)
{

    /* Child (continued) */
    
    NSFileHandle *readFileHandle = nil;
    uint32_t dataLength = 0;
    NSData *readData = nil;
    
    readFileHandle = [[[NSFileHandle alloc] initWithFileDescriptor: atoi(argv[1])] autorelease];
    readData = [readFileHandle readDataOfLength: sizeof(dataLength)];
        assert(readData);
    [readData getBytes: &dataLength length: sizeof(dataLength)];
    
    readData = [readFileHandle readDataOfLength: dataLength];
        assert(readData);
    
    NSLog(@"Child: read correct amount of data: %@ (%d bytes)", ([readData length] == DATA_LENGTH ? @"yes!" : @"no!"), [readData length]);

}
[pool release];
return 0;

}

</source>


/*

asynctask.m – sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask

compile with: gcc -Wall -O3 -x objective-c -fobjc-exceptions -framework Foundation -o asynctask asynctask.m

Before running ./asynctask you may want to create testfile.txt as shown below and customize the code of the main() function at the bottom before compilation.

./asynctask ./asynctask > asynctask-output.txt 2>&1 for ((i=0; i < 20; i++)); do ./asynctask; done

open -e asynctask-output.txt

asynctask.m is based on and extends FRCommand.h and FRCommand.m by Torsten Curdt (Apache License, Version 2.0).

Being a GUI-less application (i.e. a Foundation-based command line tool), asynctask.m runs an NSRunLoop manually to enable the use of asynchronous “waitForDataInBackgroundAndNotify” notifications. In addition, asynctask.m uses pthread_create(3) and pthread_detach(3) for writing more than 64 KB to the stdin of an NSTask.

Source code:

create testfile.txt

jot -b “line” 500 > testfile.txt
jot -b “line” 20000 > testfile.txt
jot -b “line” 1000000 > testfile.txt
du -h testfile.txt

*/

/*

//#import <Cocoa/Cocoa.h>

#import <Foundation/Foundation.h> #import #import #import

// ADDED begin

static int empty_data_count; static int task_exit_code;

struct arg_struct { NSData *inData; NSFileHandle *inHandle; };

void *threadFunction(void *arguments) { [[NSAutoreleasePool alloc] init]; struct arg_struct *taskArgs = arguments; [taskArgs -> inHandle writeData: taskArgs -> inData]; [taskArgs -> inHandle closeFile]; //[taskArgs -> inHandle release]; //[taskArgs -> inData release]; return nil; }

// ADDED end

@interface FRCommand : NSObject { NSTask *task; NSString *path; NSArray *args; NSData *stdinData; // ADDED NSMutableString *output; NSMutableString *error; BOOL terminated; NSFileHandle *outFile; NSFileHandle *errFile; NSFileHandle *stdinHandle; // ADDED }

-(NSData *) availableDataOrError: (NSFileHandle *)file;

//#import “FRCommand.h”

@implementation FRCommand

}

// ADDED // For “availableDataOrError:” see: // - “NSTasks, NSPipes, and deadlocks when reading…”, // http://dev.notoptimal.net/2007/04/nstasks-nspipes-and-deadlocks-when.html // - “NSTask stealth bug in readDataOfLength!! :(“, // http://www.cocoabuilder.com/archive/cocoa/173348-nstask-stealth-bug-in-readdataoflength.html#173647

-(NSData ) availableDataOrError: (NSFileHandle *)file { for (;;) { @try { return [file availableData]; } @catch (NSException *e) { if ([[e name] isEqualToString:NSFileHandleOperationException]) { if ([[e reason] isEqualToString: @”** -[NSConcreteFileHandle availableData]: Interrupted system call”]) { continue; } return nil; } @throw; } } // for }

-(void) appendDataFrom:(NSFileHandle)fileHandle to:(NSMutableString)string {

NSData *data = [self availableDataOrError: fileHandle]; //NSData *data = [fileHandle availableData]; if ([data length]) { NSString *s = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding: NSUTF8StringEncoding]; //NSString *s = [[NSString alloc] initWithBytes:[data bytes] length:[data length] encoding: NSASCIIStringEncoding];

  // ADDED  (originally only [output appendString: s];)
  // cf. http://www.karlkraft.com/index.php/2008/01/07/equality-vs-identity/
  if (fileHandle == outFile)
      [output appendString: s];
  else if(fileHandle == errFile)
      [error appendString: s];
  else
      NSLog(@"missing stdout / stderr error: fileHandle does not match outFile or errFile\n");

  //NSLog(@"| %@", s);    // uncomment for: /usr/bin/tail -f /path/to/test.log
  //fprintf(stderr, "\r\033[0K%s", [s UTF8String]);   // uncomment for: curl -L -O ...
  [s release];
  //[fileHandle waitForDataInBackgroundAndNotify];    }else{
  empty_data_count += 1;
  if (empty_data_count > 10)
  {   
     //[task interrupt];   // failed to abort infinite NSRunLoop
     //[task terminate];   // same
     [[NSNotificationCenter defaultCenter] removeObserver:self];  // only way to abort infinite NSRunLoop ???
  }    }

[fileHandle waitForDataInBackgroundAndNotify];

}

-(void) outData: (NSNotification ) notification { //NSLog(@”outData:\n”); NSFileHandle *fileHandle = (NSFileHandle) [notification object]; [self appendDataFrom:fileHandle to:output]; [fileHandle waitForDataInBackgroundAndNotify]; }

-(void) errData: (NSNotification ) notification { NSFileHandle *fileHandle = (NSFileHandle) [notification object]; //[self appendDataFrom:fileHandle to: output]; [self appendDataFrom:fileHandle to: error]; // ADDED [fileHandle waitForDataInBackgroundAndNotify]; }

}

-(void)dealloc { [task release]; [super dealloc]; }

@end

int main(int argc, const char *argv[]) {

NSAutoreleasePool *mainpool = [[NSAutoreleasePool alloc] init];

// Test 1: write data to stdin of NSTask for subsequent cat(1) or tail(1)

NSString *testfile = @”testfile.txt”;
NSData *data = [NSData dataWithContentsOfFile: testfile];

FRCommand *newTask = [[[FRCommand alloc] initWithPath: @”/bin/cat”] autorelease]; //[newTask setArgs: [NSArray arrayWithObjects: @”-n”, nil ; [newTask setArgs: [NSArray arrayWithObjects: @”-u”, @”-n”, nil ]];

//FRCommand *newTask = [FRCommand alloc] initWithPath: @”/usr/bin/tail”] autorelease]; //[newTask setArgs: [NSArray arrayWithObjects: @”-n”, @”10”, nil ;

[newTask setInput: data];

// Test 2: do not write to stdin

/* // ls FRCommand *newTask = [FRCommand alloc] initWithPath: @”/bin/ls”] autorelease]; //[newTask setArgs: [NSArray arrayWithObjects: @”-l”, nil ; //[newTask setArgs: [NSArray arrayWithObjects: @”-l”, @”fileDoesNotExist”, nil ]]; [newTask setArgs: [NSArray arrayWithObjects: @”-ld”, @”/”, @”fileDoesNotExist”, nil ]];

// tail -f test.log // uncomment the following line above: NSLog(@”| %@”, s);
//FRCommand *newTask = [FRCommand alloc] initWithPath: @”/usr/bin/tail”] autorelease]; //[newTask setArgs: [NSArray arrayWithObjects: @”-f”, @”/path/to/test.log”, nil ;

// curl -L -O http://mirror.ctan.org/systems/mac/mactex/MacTeX.mpkg.zip // uncomment the following line above: fprintf(stderr, “\r\033[0K%s”, [s UTF8String]); //FRCommand *newTask = [FRCommand alloc] initWithPath: @”/usr/bin/curl”] autorelease]; //[newTask setArgs: [NSArray arrayWithObjects: @”-L”, @”-O”, @”http://mirror.ctan.org/systems/mac/mactex/[[MacTeX.mpkg.zip”, nil ]];

*/

//———————-

NSMutableString *stdoutString = [NSMutableString string]; NSMutableString *stderrString = [NSMutableString string];

[newTask setOutput: stdoutString]; [newTask setError: stderrString];

[newTask execute];

NSLog(@”stdoutString:\n%@”, stdoutString); NSLog(@”stderrString:\n%@”, stderrString);

[mainpool release];

printf(“\nempty_data_count: %i\n”, empty_data_count); printf(“\ntask_exit_code: %i\n\n”, task_exit_code);

return 0;

}

</source>