Result of a bash command executed through NSTask - objective-c

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){}

Related

"Launch path not accessible" using NSTask to create Git commit

I am trying to use NSTask to create a Git commit and to add a message to that commit.
This is the code I have tried.
NSString *projectPath = #"file:///Users/MYNAME/Desktop/MYPROJECT/";
//stage files
NSPipe *pipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
task.launchPath = projectPath;
task.arguments = #[#"git", #"add", #"."];
task.standardOutput = pipe;
[task launch];
//commit
NSPipe *pipe2 = [NSPipe pipe];
NSTask *task2 = [[NSTask alloc] init];
task2.launchPath = projectPath;
task2.arguments = #[#"git", #"commit", #"-m",#"\"Some Message\""];
task2.standardOutput = pipe2;
[task2 launch];
I received projectPath by using NSOpenPanel (standard OS X open dialog).
In the Xcode terminal, I get the message
"launch path not accessible"
So what am I doing wrong?
Update
After comment from Josh Caswell this is my code
NSString *projectPath = #"file:///Users/MYNAME/Desktop/MYPROJECT/";
NSString *gitPath = #"/usr/local/bin/git"; //location of the GIT on my mac
//stage
NSPipe *pipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
task.launchPath = gitPath;
task.currentDirectoryPath = projectPath;
task.arguments = #[#"add", #"."];
task.standardOutput = pipe;
[task launch];
After [task launch]; I get error message in terminal "working directory doesn't exist."
The task's launchPath is the path to the program you want to run: that's Git here, so that path probably needs to be /usr/local/bin/git. And remove #"git" from the arguments; it's not an argument, it's the executable.
The path to your project should be used for the task's currentDirectoryPath so that it has the correct working directory.
With the pointers from Josh Casswell's answer I managed to figure it out. I found out that I have to remove the file:// part from the projectPath. So project path should be #"/Users/MYNAME/Desktop/MYPROJECT/".
Also it should not contain spaces because it does not work with %20 escape character. Which is kind of strange because when you use NSOpenPanel you get NSURL and when you call absolute path on it you get the "file://" at the start and "%20" instead of spaces inside of the path.
TLDR;
This code works in Xcode 8:
NSString *projectPath = #"/Users/MYNAME/Desktop/MYPROJECT/"; //be careful that it does not contain %20
NSString *gitPath = #"/usr/local/bin/git";
NSString *message = #"this is commit message";
//stage
NSPipe *pipe = [NSPipe pipe];
NSTask *task = [[NSTask alloc] init];
task.launchPath = gitPath;
task.currentDirectoryPath = projectPath;
task.arguments = #[#"add", #"."];
task.standardOutput = pipe;
[task launch];
[task waitUntilExit];
//commit
NSPipe *pipe2 = [NSPipe pipe];
NSTask *task2 = [[NSTask alloc] init];
task2.launchPath = gitPath;
task2.currentDirectoryPath = projectPath;
task2.arguments = #[#"commit", #"-m", message];
task2.standardOutput = pipe2;
[task2 launch];
[task2 waitUntilExit];
Update
Add [task waitUntilExit];
if you keep getting the message
fatal: Unable to create
'/Users/MYNAME/Desktop/MYPROJECT/.git/index.lock': File exists.
Another git process seems to be running in this repository, e.g. an
editor opened by 'git commit'. Please make sure all processes are
terminated then try again. If it still fails, a git process may have
crashed in this repository earlier: remove the file manually to
continue.

NSTask hangs on readDataToEndOfFile

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

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.

How to use NSTask with pbcopy?

I am a beginner and I have a problem. I would like to use NSTask with the command "pbcopy". I tried this but it seems that it doesn't work :
NSTask *task;
task = [[NSTask alloc] init];
[task setLaunchPath: #"/bin/echo"];
NSArray *arguments;
arguments = [NSArray arrayWithObjects: #"my-text-to-copy", #"| pbcopy", nil];
[task setArguments: arguments];
[task launch];
Any ideas ? Thanks.
It works fine :
NSTask *task = [[NSTask alloc] init];
NSPipe *pipe;
pipe = [NSPipe pipe];
task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/echo"];
[task setStandardOutput:pipe]; // write to pipe
[task setArguments: [NSArray arrayWithObjects: #"tmp", nil]];
[task launch];
[task waitUntilExit];
task = [[NSTask alloc] init];
[task setLaunchPath:#"/usr/bin/pbcopy"];
[task setStandardInput:pipe]; // read from pipe
[task launch];
[task waitUntilExit];
The pipe ("|") is a feature of the shell, not an argument to the command you're using. You have to use two NSTasks, one for echo and one for pbcopy and set up an NSPipe between them.
Btw, I'm assuming that you're just using this as an example. Otherwise it would be much simpler to use NSPasteboard for this.