Cannot read stdout from an NSTask in release mode - objective-c

I'm reading the stdout of an NSTask like so:
NSPipe *outputpipe = [[NSPipe alloc] init];
NSFileHandle *output = [outputpipe fileHandleForReading];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedOutputFromAlgo:) name:NSFileHandleReadCompletionNotification object:output];
NSTask* task = [[NSTask alloc] init];
[task setLaunchPath:[NSString stringWithFormat:#"%#algo/Algo", kVinylRoot]];
[task setArguments:[NSArray arrayWithObject:plist]];
[task setStandardOutput:outputpipe];
[task setCurrentDirectoryPath:[NSString stringWithFormat:#"%#algo", kVinylRoot]];
[task launch];
[output readInBackgroundAndNotify];
[task waitUntilExit];
...
-(void)receivedOutputFromAlgo:(NSNotification*)notification {
[[notification object] readInBackgroundAndNotify];
NSData* dataOutput = [[notification userInfo] objectForKey:NSFileHandleNotificationDataItem];
NSString* dataString = [[NSString alloc] initWithData:dataOutput encoding:NSStringEncodingConversionAllowLossy];
//Do stuff
}
This works just fine if I run this from Xcode. I see the stdout of my task in the console, and receivedOutputFromAlgo is hit multiple times. However, if I archive the app and run it by double clicking the .app package or run it from terminal it doesn't work. I can still see the task stdout coming through in console.app or in the terminal if I run it from there.
Is it not considered stdout at this point? Why would this not work?
Edit: I just tried turning off optimization in release and that didn't work either.

you need handle the setStandardError, like this:
[task setStandardError:outputpipe];

Related

Cocoa wrapper for an interactive Unix command

Ok, so I know you can make an NSTask to run command line tools with Objective-C:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/gdb"];
[task launch];
I'm just wondering if there's a way to communicate with interactive command line tools such a as gdb. This would involve giving the command inputs based on user interaction (like run, kill or quit with gdb) and then reacting based on the information it outputs.
You can use NSTask's setStandardInput:, setStandardOutput: and setStandardError: selectors in conjunction with NSPipe instances to communicate with the launched program.
For example, to read the task's output:
task = [[NSTask alloc] init];
[task setStandardOutput: [NSPipe pipe]];
[task setStandardError: [task standardOutput]]; // Get standard error output too
[task setLaunchPath: #"/usr/bin/gdb"];
[task launch];
You can then obtain an NSFileHandle instance that you can use to read the task's output with:
NSFileHandle *readFromMe = [[task standardOutput] fileHandleForReading];
To set up a pipe for sending commands to gdb, you would add
[task setStandardInput: [NSPipe pipe]];
before you launch the task. Then you get the NSFileHandle with
NSFileHandle *writeToMe = [[task standardInput] fileHandleForWriting];
Use setStandardInput: and setStandardOutput: methods of NSTaks class.
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/usr/bin/gdb"];
NSPipe *outputpipe=[[NSPipe alloc]init];
NSPipe *errorpipe=[[NSPipe alloc]init];
NSFileHandle *output,*error;
[task setArguments: arguments];
[task setStandardOutput:outputpipe];
[task setStandardError:errorpipe];
NSLog(#"%#",arguments);
output=[outputpipe fileHandleForReading];
error=[errorpipe fileHandleForReading];
[task launch];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedData:) name: NSFileHandleReadCompletionNotification object:output];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedError:) name: NSFileHandleReadCompletionNotification object:error];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(TaskCompletion:) name: NSTaskDidTerminateNotification object:task];
//[input writeData:[NSMutableData initWithString:#"test"]];
[output readInBackgroundAndNotify];
[error readInBackgroundAndNotify];
[task waitUntilExit];
[outputpipe release];
[errorpipe release];
[task release];
-(void) receivedData:(NSNotification*) rec_not {
NSFileHandle *out=[[task standardOutput] fileHandleForReading];
NSData *dataOutput=[[rec_not userInfo] objectForKey:NSFileHandleNotificationDataItem];
if( !dataOutput)
NSLog(#">>>>>>>>>>>>>>Empty Data");
NSString *strfromdata=[[NSString alloc] initWithData:dataOutput encoding:NSUTF8StringEncoding];
[out readInBackgroundAndNotify];
[strfromdata release];
}
/* Called when there is some data in the error pipe */
-(void) receivedError:(NSNotification*) rec_not {
NSFileHandle *err=[[task standardError] fileHandleForReading];
NSData *dataOutput=[[rec_not userInfo] objectForKey:NSFileHandleNotificationDataItem];
if( !dataOutput)
NSLog(#">>>>>>>>>>>>>>Empty Data");
else {
NSString *strfromdata=[[NSString alloc] initWithData:dataOutput encoding:NSUTF8StringEncoding];
[strfromdata release];
}
[err readInBackgroundAndNotify];
}
/* Called when the task is complete */
-(void) TaskCompletion :(NSNotification*) rec_not {
NSLog(#"task ended");
}

NSTask with multiple pipe input

I'm trying to use pipes to handle a command that requires multiple inputs, but not quite sure how to do it. Here's a snippet of what I'm trying to do. I know how to handle the first input, but I'm at lost as to piping in the second input -newstdinpass
NSTask *task = [[NSTask alloc] init];
NSPipe *pipe = [NSPipe pipe];
[task setLaunchPath: #"/bin/sh"];
[task setArguments: [NSArray arrayWithObjects: #"-c", #"/usr/bin/hdiutil chpass -oldstdinpass -newstdinpass /path/to/dmg", nil]];
[task setStandardInput:pipe];
[task launch];
[[pipe fileHandleForWriting] writeData:[#"thepassword" dataUsingEncoding:NSUTF8StringEncoding]];
[[pipe fileHandleForWriting] closeFile];
[task waitUntilExit];
[task release];
So I know using hdiutil in this manner is a bit of a hack, but in terms of pipes, am I going about it the correct way?
Thanks.
UPDATE: In case others are wondering about this, a quick solution to my problem is to pass in a null-terminated string as Ken Thomases had pointed out below. Use [[NSString stringWithFormat:#"oldpass\0newpass\0"] dataUsingEncoding:NSUTF8StringEncoding] into the pipe. Now, still need to learn how to bridge multiple NSTasks with pipes...
You can create multiple NSTasks and a bunch of NSPipes and hook them together, or you can use the sh -c trick to feed a shell a command, and let it parse it and set up all the IPC.
Example :
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/sh"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"-c",
#"cat /usr/share/dict/words | grep -i ham | rev | tail -5", nil];
[task setArguments: arguments];
// and then do all the other jazz for running an NSTask.
Reference : http://borkware.com/quickies/one?topic=nstask
Now, as for a "proper" command execution function, here's one I'm usually using...
Code :
/*******************************************************
*
* MAIN ROUTINE
*
*******************************************************/
- (void)runCommand:(NSString *)cmd withArgs:(NSArray *)argsArray
{
//-------------------------------
// Set up Task
//-------------------------------
if (task) { [self terminate]; }
task = [[NSTask alloc] init];
[task setLaunchPath:cmd];
[task setArguments:argsArray];
[task setStandardOutput:[NSPipe pipe]];
[task setStandardError:[task standardOutput]];
//-------------------------------
// Set up Observers
//-------------------------------
[PP_NOTIFIER removeObserver:self];
[PP_NOTIFIER addObserver:self
selector:#selector(commandSentData:)
name: NSFileHandleReadCompletionNotification
object: [[task standardOutput] fileHandleForReading]];
[PP_NOTIFIER addObserver:self
selector:#selector(taskTerminated:)
name:NSTaskDidTerminateNotification
object:nil];
//-------------------------------
// Launch
//-------------------------------
[[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify];
[task launch];
}
/*******************************************************
*
* OBSERVERS
*
*******************************************************/
- (void)commandSentData:(NSNotification*)n
{
NSData* d;
d = [[n userInfo] valueForKey:NSFileHandleNotificationDataItem];
if ([d length])
{
NSString* s = [[NSString alloc] initWithData:d encoding:NSUTF8StringEncoding];
NSLog(#"Received : %#",s);
}
[[n object] readInBackgroundAndNotify];
}
- (void)taskTerminated:(NSNotification*)n
{
[task release];
task = nil;
}
Your use of the pipe looks correct to me.
I'm not sure why you're using /bin/sh. Just set up the NSTask with its launch path being #"/usr/bin/hdiutil" and with its arguments being an array of #"chpass", #"-oldstdinpass", #"-newstdinpass", and #"/path/to/dmg". This is much safer. For example, what if the path to the dmg contains a character the shell would interpret, like $?
Unless you specifically want to take advantage of a shell feature, don't use the shell.

NSTask to retrieve the output of an external command stopped woking on Lion

The following code was working fine until I upgraded to OSX Lion. It called an external command and saved the output into a NSString.
I have no idea why it stopped working. Any ideas?
-(NSString *) rawResponse{
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/sbin/scselect"];
NSPipe *pipe = [NSPipe pipe];
[task setStandardError:pipe];
[task launch];
NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
[task waitUntilExit];
[task release];
NSString *result = [[[NSString alloc] initWithData:data
encoding:NSUTF8StringEncoding] autorelease];
NSLog(#"The returned value is: %#", result);
return result;
}
I just found out. I was assigning the NSPipe to standard error, because in Snow Leopard /usr/sbin/scselect was sending its output there, instead of standard output. Apparently the new version in Lion fixes this (and breaks my code).

NSTask not returning [duplicate]

This question already has answers here:
NSTask NSPipe - objective c command line help
(2 answers)
Closed 2 years ago.
I use a NSTask as follows:
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/bash"];
NSMutableString *script = ... // actual script here, works fine
NSArray *args = [NSArray arrayWithObjects:#"-l",
#"-c",
script,
nil];
[task setArguments: args];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
[task launch];
NSData *data = [[pipe fileHandleForReading] readDataToEndOfFile];
[task waitUntilExit];
[task release];
Works fine so far, only thing is, that somehow, the method isn't called anymore after first reaching this point. If I don't launch the task, everything's ok, so it would seem, that the task somehow blocks further execution.
Has anybody an idea, why that would be? Any hints appreciated!
Get more control on the task by running a loop where you can read the data, check if it's timed out , etc.:
while ([task isRunning])
{
NSData* data = [[pipe fileHandleForReading] availableData];
if ([data length] > 0)
{
//process the data
}
// Run current runloop in default mode; check the timeout; interrupt the task if needed
}

NSTask NSPipe - objective c command line help

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
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.
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.