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.
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
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:
“NSPipe (NSFileHandle) writedata limit?” (April 09, 2010), http://www.cocoabuilder.com/archive/cocoa/284765-nspipe-nsfilehandle-writedata-limit.html, http://pastie.org/911738 (by Dave Keck)
“64KB limit in passed in text for a service using NSTask” (August 20, 2010), http://www.cocoabuilder.com/archive/cocoa/292247-64kb-limit-in-passed-in-text-for-service-using-nstask.html
http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man3/pthread.3.html
Todo:
*/
// This sample code is based on a code snippet by Dave Keck, http://pastie.org/911738.
#import <Foundation/Foundation.h>
#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
#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:
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
// 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
(id) initWithPath:(NSString*)pPath { self = [super init]; if (self != nil) { task = [[NSTask alloc] init]; args = [NSArray array]; path = pPath; stdinData = nil; // ADDED error = nil; output = nil; terminated = NO; }
return self;
}
// 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) setArgs:(NSArray*)pArgs { args = pArgs; }
(void) setInput:(NSData*)pInput // ADDED
{
stdinData = pInput;
}
(void) setError:(NSMutableString*)pError { error = pError; }
(void) setOutput:(NSMutableString*)pOutput { output = pOutput; }
-(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) terminated: (NSNotification *)notification { NSLog(@”Task terminated”); [[NSNotificationCenter defaultCenter] removeObserver:self]; terminated = YES; }
(int) execute { empty_data_count = 0;
[task setLaunchPath:path]; [task setArguments:args];
NSPipe *outPipe = [NSPipe pipe]; NSPipe *errPipe = [NSPipe pipe]; [task setStandardOutput:outPipe]; [task setStandardError:errPipe]; [task setCurrentDirectoryPath:@”.”]; // ADDED
//NSFileHandle *outFile = [outPipe fileHandleForReading]; // TROUBLEMAKER //NSFileHandle *errFile = [errPipe fileHandleForReading]; outFile = [outPipe fileHandleForReading]; errFile = [errPipe fileHandleForReading];
// ADDED
// create inPipe after outPipe & errPipe
NSPipe *inPipe = [NSPipe pipe];
stdinHandle = [inPipe fileHandleForWriting];
[task setStandardInput:inPipe];
struct arg_struct taskArguments; taskArguments.inData = stdinData; taskArguments.inHandle = stdinHandle;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(terminated:) name:NSTaskDidTerminateNotification object:task];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(outData:) name:NSFileHandleDataAvailableNotification object:outFile];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(errData:) name:NSFileHandleDataAvailableNotification object:errFile];
[outFile waitForDataInBackgroundAndNotify]; [errFile waitForDataInBackgroundAndNotify];
[task launch];
// ADDED pthread_t thread = nil;
if (pthread_create(&thread, nil, (void ()(void *))threadFunction, (void *)&taskArguments) != 0) { perror(“pthread_create failed”); return 1; }
if (pthread_detach(thread) != 0) { perror(“pthread_detach failed”); return 1; }
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
while(!terminated) { //if (![[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:100000) if (!NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture) { break; } [pool release]; pool = NSAutoreleasePool alloc] init]; }
[pool release];
[self appendDataFrom:outFile to:output]; [self appendDataFrom:errFile to:error];
int result = [task terminationStatus];
task_exit_code = result;
return result;
}
-(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>