1

I'm trying to use NSTask to run the UNIX 'apropos' command. Here's my code:

NSTask *apropos = [[NSTask alloc] init];
NSPipe *pipe = [[NSPipe alloc] init];
[apropos setLaunchPath:@"/usr/bin/apropos"];
[apropos setArguments:[NSArray arrayWithObjects:@"filename", @"match", nil]];
[apropos setStandardOutput:pipe];
[apropos launch];
[apropos waitUntilExit];

The problem is that this never returns. I also tried using Apple's example code (TaskWrapper) and it returns the output (in three segments) but it never calls the processFinished handler.

Furthermore, the appendOutput: handler receives duplicates. So, for example, if apropos returns this:

1 2 3 4 5

I might receive something like this:

1 2 3

1 2 3 4

5

(grouped into 3 append messages).

I note that Apropos displays the output in a format where it's possible to scroll up and down in the command line instead of just directly outputting the data straight to standard output; how do I read this reliably through NSTask and NSPipe?

jstm88
  • 3,335
  • 4
  • 38
  • 55
  • Concerning the scrolling output, I assume that apropos is piping its output through less; is there a way to force it not to do this? That might be the solution. – jstm88 Jan 22 '11 at 01:32
  • apropos uses the PAGER environment variable which defaults to less, so setting PAGER to something more apropos like cat would work :) – Jason Coco Jan 22 '11 at 01:43
  • Well, the app can't modify environment variables (it's meant for an app for users to install). – jstm88 Jan 22 '11 at 01:47

2 Answers2

3

I’ve just tested this program and it works fine: the program terminates and /tmp/apropos.txt contains the output of apropos.

#import <Foundation/Foundation.h>

int main()
{
    NSAutoreleasePool *pool = [NSAutoreleasePool new];

    NSTask *apropos = [[[NSTask alloc] init] autorelease];
    NSPipe *pipe = [[[NSPipe alloc] init] autorelease];
    NSFileHandle *readHandle = [pipe fileHandleForReading];
    [apropos setLaunchPath:@"/usr/bin/apropos"];
    [apropos setArguments:[NSArray arrayWithObjects:@"filename", @"match", nil]];
    [apropos setStandardOutput:pipe];
    [apropos launch];
    [apropos waitUntilExit];

    NSString *output = [[[NSString alloc]
        initWithData:[readHandle readDataToEndOfFile]
            encoding:NSUTF8StringEncoding] autorelease];

    [output writeToFile:@"/tmp/apropos.txt" atomically:YES
        encoding:NSUTF8StringEncoding error:NULL];

    [pool drain];
    return 0;
}

Are you by any chance using NSLog() to inspect the output? If so, you might need to set a pipe for stdin as explained in this answer of mine to an NSTask related question. It seems that NSLog() sending data to stderr affects NSTask.

Community
  • 1
  • 1
  • Thanks so much. That was exactly it; I modified the Apple code to set a pipe to stdin on the NSTask and now it works fine while using NSLog to show the output. I'll remember in the future to set an empty pipe on the input if I don't use it. :) – jstm88 Jan 22 '11 at 02:46
  • @bavarious : you save my day! Could jfm429 elaborate on how to set the .. that pipe stuff? currently I just disable the NSLog – swdev Aug 23 '11 at 09:12
  • You just saved me a lot of time. Thanks! – davidb Sep 28 '15 at 15:31
2

With your original code, I would imagine it's because you're not reading the output of the command. The pipes only have a limited buffer size, and if you don't read the output of the task, it can end up hung waiting for the buffer to empty out. I don't know anything about the sample code you tried so I can't help there. As for the last question, apropos only uses the pager when it's connected to a terminal. You're not emulating a terminal, so you don't have to worry. You can prove this by running apropos whatever | cat in the terminal and verifying that the pager is not invoked.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • http://developer.apple.com/library/mac/#samplecode/Moriarity/Introduction/Intro.html%23//apple_ref/doc/uid/DTS10000396 has the sample code I'm using. – jstm88 Jan 22 '11 at 01:50
  • Also, I'm not reading it wrong AFAIK. If I execute `apropos filename match > test.txt` from the Terminal and then use '/usr/cat' and '/Users/Justin/text.txt' as the NSTask arguments (without any additional modifications to the NSTask code) then it works perfectly. So it seems somehow output from apropos through NSTask gets mangled but output from cat through the exact same code is handled just fine. – jstm88 Jan 22 '11 at 01:52