3

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.

Anonymous
  • 1,750
  • 3
  • 16
  • 21

2 Answers2

3

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.

CRD
  • 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
0

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);
}
Motti Shneor
  • 2,095
  • 1
  • 18
  • 24