10

Here is my code:

task = [[NSTask alloc] init];
[task setCurrentDirectoryPath:@"/applications/jarvis/brain/"];
[task setLaunchPath:@"/applications/jarvis/brain/server.sh"];

NSPipe * out = [NSPipe pipe];
[task setStandardOutput:out];

[task launch];
[task waitUntilExit];
[task release];

NSFileHandle * read = [out fileHandleForReading];
NSData * dataRead = [read readDataToEndOfFile];
NSString * stringRead = [[[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding] autorelease];

So I'm trying to replicate this:

cd /applications/jarvis/brain/
./server.sh

but using NSTask in objective-c.

For some reason though, when I run this code, stringRead, returns nothing. It should return what terminal is returning when I launch the .sh file. Correct?

Any ideas?

Elijah

objectiveccoder001
  • 2,929
  • 10
  • 43
  • 71
  • Are you sure the server.sh script outputs on standard out? Maybe you should hook up stderr as well and see if that contains anything. You may also want to consider reading data from the pipe as the task is running, because if it tries to print too much to the pipe while you aren't reading, and the buffer fills up, the task will hang the next time it tries to output anything. – Lily Ballard Aug 09 '10 at 21:07
  • I'm not sure. Can you show me an example? Yes, I removed the [task release] and [task waitUntilExit]. Same problem. – objectiveccoder001 Aug 09 '10 at 21:23
  • Are you checking the contents of stringRead programmatically (or in gdb), or are you attempting to print them out using NSLog or something? If you are using NSLog and are seeing no output at all, go check the Console log in Applications > Utilities for your output. Shell scripts run as NSTask can make the Xcode console output stop working. Other than that, I second Kevin's opinion to also check if there's something on standard error (simply add a second pipe and set that as standard error of your task), and to not rely on the pipe being being able to buffer all of your task's output. – puzzle Aug 09 '10 at 21:39
  • Programmatically and through NSRunAlertPanel(); just for testing. Ok. I'll try that as well. What other options are there for the output? Can you post any examples that might help? – objectiveccoder001 Aug 09 '10 at 21:48
  • And I tried that, same problem....any ideas? – objectiveccoder001 Aug 09 '10 at 22:16
  • Put a very simple shell script, e.g. one containing just `echo "Hello, World"`, in place of server.sh. Can you see that output? If you can, how is the output of server.sh done differently? – puzzle Aug 09 '10 at 23:26
  • Ok tried entering: "/bin/ls" and it returned correctly. But when I enter "date" it freezes and doesn't get passed the init. Any ideas? – objectiveccoder001 Aug 09 '10 at 23:49
  • Echo "hello world" also freezes the app – objectiveccoder001 Aug 09 '10 at 23:51

2 Answers2

20

Xcode Bug
There's a bug in Xcode that stops it from printing any output after a a new task using standard output is launched (it collects all output, but no longer prints anything). You're going to have to call [task setStandardInput:[NSPipe pipe]] to get it to show output again (or, alternatively, have the task print to stderr instead of stdout).


Suggestion for final code:

NSTask *server = [NSTask new];
[server setLaunchPath:@"/bin/sh"];
[server setArguments:[NSArray arrayWithObject:@"/path/to/server.sh"]];
[server setCurrentDirectoryPath:@"/path/to/current/directory/"];

NSPipe *outputPipe = [NSPipe pipe];
[server setStandardInput:[NSPipe pipe]];
[server setStandardOutput:outputPipe];

[server launch];
[server waitUntilExit]; // Alternatively, make it asynchronous.
[server release];

NSData *outputData = [[outputPipe fileHandleForReading] readDataToEndOfFile];
NSString *outputString = [[[NSString alloc] initWithData:outputData encoding:NSUTF8StringEncoding] autorelease]; // Autorelease optional, depending on usage.
Itai Ferber
  • 22,011
  • 5
  • 56
  • 67
  • It seems like this is freezing my code as well...huh...any other ideas? – objectiveccoder001 Aug 13 '10 at 04:06
  • And, I tried to put in echo "hello world" it returned blank....and date retured blank. – objectiveccoder001 Aug 13 '10 at 04:09
  • Even with `[server setStandardInput:[NSPipe pipe]]`? That's a little bit strange. Did you copy my code verbatim? Try it, and see if it'll work with a file that just contains `echo "Hello World"; pwd | ls`. If it still doesn't work, please tell us what version of Xcode you're using, and see if writing the string to file produces any output (it might just be the Xcode terminal not printing any more output). – Itai Ferber Aug 13 '10 at 08:15
  • 1
    Years later this bug seems to have resurfaced, and this answer saved me! Thanks! – Pokey McPokerson Jun 20 '14 at 11:31
  • 1
    @PokeyMcPokerson That's great! Glad I could help, preemptively. ;) – Itai Ferber Jun 20 '14 at 21:53
10

The solution above is freezing because it's synchronous. Call to [server waitUntilExit] blocks the run loop until the tasks is done.

Here's the async solution for getting the task output.

task.standardOutput = [NSPipe pipe];
[[task.standardOutput fileHandleForReading] setReadabilityHandler:^(NSFileHandle *file) {
    NSData *data = [file availableData]; // this will read to EOF, so call only once
    NSLog(@"Task output! %@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    // if you're collecting the whole output of a task, you may store it on a property
    [self.taskOutput appendData:data];
}];

Probably you want to repeat the same above for task.standardError.

IMPORTANT:

When your task terminates, you have to set readabilityHandler block to nil; otherwise, you'll encounter high CPU usage, as the reading will never stop.

[task setTerminationHandler:^(NSTask *task) {

    // do your stuff on completion

    [task.standardOutput fileHandleForReading].readabilityHandler = nil;
    [task.standardError fileHandleForReading].readabilityHandler = nil;
}];

This is all asynchronous (and you should do it async), so your method should have a ^completion block.

supermarin
  • 638
  • 6
  • 11