What's the best way to directly pipe an NSTask's output to a file? I want to go through as few buffers in memory as possible.
2 Answers
Before launching the NSTask
set the standard output and standard error (setStandardOutput:
, setStandardError:
) to an NSFileHandle
for the output file (or files if you want a different one for each output).
An NSFileHandle
is just a wrapper for the underlying OS file descriptor and the NSTask
will do the appropriate work to connect that to the specified output (i.e. it will most likely do a dup(2)). No intermediate memory buffers should be introduced.
Once the NSTask
is started you can (and should) close the NSFileHandle
in your code.

- 52,522
- 5
- 70
- 86
-
Why would you need to close the `NSFileHandle`? Doesn't closing it mean it won't write any more data to the file? That way the data generated by the command won't be written, isn't that right? – Alex Oct 28 '12 at 14:08
-
@Alex - no. The underlying OS file descriptor is a *reference* to an open file/pipe/socket. Some of these dscptrs have fixed usage; standard input, output & error; others are allocated as a process opens files (the dscptr is actually a small integer, 0 for standard input, 1 for std output & 2 for std error). What `setStandardOutput:` and friends do is take an arbitrary dscptr and *duplicate* it onto the corresponding std dscptr of the new task's process. After the process has started you close the original dscptr leaving the file open on the std one. See a C/Unix guide for more detail. – CRD Oct 28 '12 at 18:45
-
few lines of code to demonstrate? I hardly know how to create this NSFileHandle to a new non-existing file, and make sure it will be created? or should I first create the file, open it and somehow make NSTask write to the end of it? please advise – Motti Shneor Feb 14 '21 at 14:58
-
@MottiShneor - In keeping with the SO way read the `NSFileHandle` docs etc. and then design and try some code. If you get stuck ask a new question covering your design, showing your code, and explain where you're stuck getting it to work. – CRD Feb 15 '21 at 07:39
I'll recap on CRD's correct answer, adding a code-snippet that demonstrates.
Here's a snippet that launches an NSTask
to collect some logs (using the 'log show' command-line tool, redirecting the standard output to a file without allocating and maintaining buffers to do this.
NSString *logFilePath = [@"~/Desktop/MyLog.log" stringByStandardizingPath];
NSFileHandle *outFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
if(outFileHandle == nil) {
[[NSFileManager defaultManager] createFileAtPath:logFilePath contents:nil attributes:nil];
outFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath]; // create the file if needed
} else {
[outFileHandle seekToEndOfFile]; // append to file if it already exists
}
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:@"/usr/bin/log"];
[task setArguments: @[@"show", @"--predicate", @"process == \"myProcessName\""]];
[task setStandardOutput: outFileHandle];
@try {
[task launch];
[outFileHandle closeFile]; // task already holds it, we need not.
[task waitUntilExit];
int status = [task terminationStatus];
switch (status) {
case EXIT_SUCCESS: {
if ([[NSFileManager defaultManager] fileExistsAtPath:logFilePath] == NO) {
NSLog(@"Log file not created for some reason, but task succeeded.");
}
break;
}
case EXIT_FAILURE:
default:
NSLog(@"Failed to extract logs. Error:%d", status);
break;
}
} @catch (NSException *exception) {
NSLog(@"Exception occurred running task: %@", exception);
}

- 2,095
- 1
- 18
- 24