NSTask hangs on readDataToEndOfFile - objective-c

Trying to read the data returned from an NSTask causes a hang that never returns. I've verified my script being run does in fact return data to both stdout and stderr. It's a simple two line shell script that sends one line to stdout and the other to stderr.
The NSLog output says
Got handle: <NSConcreteFileHandle: 0x10010a800>
And then it just hangs. This is the code I'm using.
NSPipe *stderr = [NSPipe pipe];
NSPipe *stdout = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
task.standardError = stderr;
task.standardOutput = stdout;
task.standardInput = [NSPipe pipe];
task.launchPath = #"/tmp/f";
[task launchPath];
[task waitUntilExit];
NSFileHandle *fh = [stderr fileHandleForReading];
NSLog(#"Got handle: %#", fh);
[fh readDataToEndOfFile];
NSLog(#"Read it");

It's because you have never actually launched your task. You call
[task launchPath];
That just returns the task's path as a string, it doesn't actually launch the task. You want
[task launch];

Tom is right, but also you need to launch the task before trying to envoke readDataToEndOfFile

Related

Result of a bash command executed through NSTask

I have this example of using NSTask in Objective-C
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/bash"];
[task setArguments:#[ #"-c", #"cp /Directory/file /users/user_name/Desktop" ]];
[task launch];
I want to know if the [task setArguments:] returns a state of success or failure for executing that command, and save the state to check afterwards. How can I get that result?
I want to know if the [task setArguments:] returns a state of success or failure for executing that command, and save the state to check afterwards.
Why do you think setting the arguments of the task, that is not yet launched and running, might return the status of running the command?
How can I get that result?
Read the documentation for NSTask, its method waitUntilExit, and its property terminationStatus.
That said, as #ItaiFerber raises in the comment, hopefully this is just an example and you are not really using NSTask to run cp.
NSPipe *pipe = [[NSPipe alloc] init];
NSFileHandle *file = pipe.fileHandleForReading;
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/bash"];
[task setArguments:#[ #"-c", #"cp /Directory/file /users/user_name/Desktop" ]];
task.standardOutput = pipe;
[task launch];
if(task.isRunning)
[task waitUntilExit];
int status = [task terminationStatus];
if(status == 0){}

Executing a command from Objective C

I want to execute a command from objective C (Cocoa framework). The command I am trying is as below. I tried with NSTask but it says "launch path not accessible" when I execute it.
sudo ifconfig en0 down
My code is:
- (void)testme {
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"sudo ifconfig en0 down"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"foo", #"bar.txt", nil];
[task setArguments: arguments];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"command returned:\n%#", string);
[string release];
[task release];
}
sudo ifconfig en0 down is not a sensible launch path. The correct launch path for this command would be /usr/sbin/sudo.
Once that is done, you still need to pass the correct arguments to setArguments:. foo and bar.txt look like example code that you copied without reading.
MORE IMPORTANTLY, THOUGH, running sudo from NSTask will not work. You will need to use Authorization Services to launch a privileged command.
You need to specify the full executable path and you should specify the arguments as the arguments, not along with the launch path. NSTask ain't a shell, it internally uses syscalls (execv(), I guess) to invoke the command.
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/sudo"];
NSArray *arguments = #[#"ifconfig", #"en0", #"down"];
[task setArguments:arguments];

Run terminal command with NSTask

I want to run a Terminal command in my program.
The command looks like this:
cd /path/to/file/; ./foo HTTPProxy 127.0.0.1
It works with system() but it doesn't work when I use NSTask.
system("cd /path/to/file/; ./foo HTTPProxy 127.0.0.1");
works fine but
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/path/to/file/./foo"];
NSPipe *pipe = [NSPipe pipe];
[task setStandardOutput:pipe];
NSFileHandle *file = [pipe fileHandleForReading];
[task setArguments:[NSArray arrayWithObjects:#"HTTPProxy 127.0.0.1", nil]];
[task launch];
NSData *data = [file readDataToEndOfFile];
NSString *string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog(string);
doesn't.
Output:
Command-line option 'HTTPProxy 127.0.0.1' with no value. Failing.
Has anybody an idea?
Now I think I have got it:
[task setArguments:[NSArray arrayWithObjects:#"HTTPProxy", #"127.0.0.1", nil]];
those are separate arguments in your invocation from the command line...
OLD ANSWER:
You could trying setting the current directory for execution:
– setCurrentDirectoryPath:
This is basically the effect of cd in the system version of your code.

Running multiple NSTasks consecutively

I need to run multiple commands in sequence using NSTask and was wondering what would be a good way to tell if a task has finished so I can continue on to the next command. I'm using "sox" (which I am including in my application bundle) to create temporary audio files using input audio files, and then need to combine those temporary audio files into a single file. An example of the flow of processes (not the actual commands):
1) songA > tempA
2) songB > tempB
3)combine tempA tempB > songC
I'm using the following code to complete the first command:
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"songA", #"-f", #"-S", #"-G", #"-V", #"-b", #"24", #"-r", #"384k", #"tempA", nil];
NSString *path=[[NSBundle mainBundle] pathForResource:#"sox" ofType:nil];
NSTask *task;
task = [[NSTask alloc] init];
[task setStandardInput:[NSPipe pipe]];
[task setLaunchPath:path];
[task setArguments: arguments1];
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
NSFileHandle *file;
file = [pipe fileHandleForReading];
[task launch];
NSData *data;
data = [file readDataToEndOfFile];
NSString *string;
string = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (#"stuff :\n%#", string);
[string release];
[task release];
Suppposing I needed to perform two more NSTask processes after this one had finished (using the output of the previous processs), what would be the best way to detect that one process has finished so that I can continue on to the next one.
Thanks.
Maybe not understand fully, but
[task waitUntilExit];
does not do the job?

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.