Cocoa/ Objective-C Shell Command Line Execution - objective-c

This is probably a stupid question, but how can I execute a shell command from my Cocoa app?
I have the command as a string "command", but can easily manipulate data as needed.
There is no need to get a returned output value.

NSTask is pretty easy to do this with. For a synchronous call, you can use something like this fragment:
NSString *path = #"/path/to/executable";
NSArray *args = [NSArray arrayWithObjects:..., nil];
[[NSTask launchedTaskWithLaunchPath:path arguments:args] waitUntilExit];
The -waitUntilExit call makes sure it finishes before proceeding. If the task can be asynchronous, you can remove that call and just let the NSTask do it's thing.

If you just want to run something and don't care about the output or return code (for example, you want to touch a file), you can just do
system("touch myfile.txt");
Easy as that.

NSTask
Using the NSTask class, your program can run another program as a subprocess and can monitor that program’s execution.

Related

Getting a list of all bootable drives mac

I'm currently looking for a way to get a list of all the bootable partitions on a Mac?
I know you can get a list of all the volumes? But I don't know how to check if each particular volume is bootable?
Is there a way to do this using swift or objective c?
With objective-c or swift probably not, but you can run an apple script (terminal commands) from your objective-c app (on OS X, I don't know whether it can be done on iOS)
This is how you execute an apple script:
//Begin of the script
NSAppleScript *script = [[NSAppleScript alloc]
initWithSource:#"Tell application \"Terminal\" \n\
do shell script \" some script here \"\n\
end tell"];
NSDictionary *errors = nil;
NSAppleEventDescriptor *result = [script executeAndReturnError:&errors];
NSLog(#"result: %#", result);
NSLog(#"errors: %#", errors);
if(errors==NULL){
NSLog(#"Succeeded");
}
else{
NSLog(#"Failed");
}
//End of the script
The quick and dirty way to do this is to shell out to either bless --info or systemsetup liststartupdisks commands. You can specify that you want the output in plist format to make it easier to parse as well.
As far as executing those commands the typical NSTask should work. You could also use system() I suppose, but it isn't really standard practice on OS X as NSTask has many advantages over it.
A purely code way to do this would be to get the list of disks and then look at each them for the known files that make OS X bootable. Things like the boot.efi file in /System/Library/CoreServices, the mach kernel file, and the contents of /System/Library/CoreServices/SystemVersion.plist.

Executing shell commands with NSTask - Objective-C Cocoa

I have been searching for days and hours for this, I have seen a lot of examples of this, but cannot figure out how NSTask works, let's say I wanted to execute the command killall Dock
or defaults write com.apple.Finder AppleShowAllFiles YES something like that, how would I go about doing this.
I know how to execute an external shell script (sh) but need to be more sophisticated and use NSTask instead.
Thanks for any help!!
You could do something like:
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/bin/bash"];
[task setArguments:#[ #"-c", #"/usr/bin/killall Dock" ]];
[task launch];
Exactly what launch path and arguments you provide are dictated by the command you want to run and its parameters.

system() call in cocoa app

In my cocoa app I have to call system() function to launch an external app. The command I use is:
system("./main &");
If I run the app from Xcode, it works fine, because I know the folder where to put main.
If I create an archive, and distribute my .app application, system() can't find "main". Where I have to put it?? Or otherwise, how can I run an app using "./" when I'm not in the folder the application is?
EDIT: Maybe I solved using NSTask, but how can I run "main" in background? Now it opens in a new terminal window.
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:#"/Applications/Multibo/main"];
[task setArguments:[NSArray arrayWithObjects:[NSString stringWithFormat:#"./main &"], nil]];
[task launch];
Thanks
Try appending the full path before the executable's name and use it as an argument to system. Note that system is implementation defined -- its behavior is not guranteed to be the same across systems and its usage is thus not recommended. You should probably look for a suitable alternative such as NSWorkspace.
While dirkgently's answer is directly correct, the real answer is more complex.
First, NSTask is not a generic command line invoker. That is why adding & doesn't do what you expect. In fact, all tasks invoked via NSTask are effectively background.
But you really don't want to use NSTask. You should really be using an XPC service. Now, if your goal is something that runs even after your program exits, you should be looking into LaunchServices.

Why does an autoreleased NSTask block the runloop on an NSOperation thread indefinitely?

I've run into an while trying to launch an NSTask from inside an NSOperation.
Here's a very simple app I've put together to showcase the problem. When you click the button, an NSOperation is queued. It sets up an NSRunLoop, and calls a method which invokes an NSTask. The task is really simple- it just launches /bin/sleep for two seconds (enough to easily see the spinner when things are working properly).
The app works as advertised, however if you change line 23 of TaskPerformer.m to an autorelease, (sorry, I'm a new poster so I can't link directly) or comment it out entirely (thus leaking the NSTask object), the NSOperation's thread will never exit. Its main runloop seems to be blocking on something.
Now, the problem here is twofold. First off, I don't understand why my thread is blocking, but moreover, if I turn on garbage collection for this application, the same behavior manifests itself. Because there's no way for me to manually release the NSTask, the thread blocks no matter what.
If someone could tell me what's going on, I'd be eternally grateful!
I see a couple different issues in the sample project you posted. In TaskPerformer.m you have:
[task waitUntilExit];
[task launch];
The waitUntilExit call is meant to be called after launching the task, and will simply block and do nothing until the task has completed. If you only care about waiting until the task has finished, and not about getting output from it or anything, then you should be able to just call launch followed by waitUntilExit, and not have to bother messing with the run loop at all.
If you do want to get output from the task though, then you'll want to get its standardOutput, which by default should return you an instance of NSFileHandle. You can then call readDataOfLength: or readDataToEndOfFile, both of which will block and return data from the process when data is available.
Since this entire thing is going to be done in a background thread anyway, it's OK that these methods block, but you wouldn't want to do the same thing on the main thread, since that would lock up the interface for the user until the task was complete. If you did this on the main thread, you'd want to use NSFileHandle's readInBackgroundAndNotify and friends. For a background thread though, using NSRunLoop shouldn't really be necessary.
I figured out what was going on here. Turns out my call to [runLoop run] was setting up the loop and running it indefinitely. It was never falling down into the while (!done) loop. Turns out that after calling run, an NSRunLoop will run until it has no more inputs. Calling release on my NSTask led to exactly this scenario, so (quite by accident) my runloop exited.
The solution was to remove [runLoop run] and simply rely on my own while loop. Hope this helps somebody else!

How lightweight is NSOperationQueue on Snow Leopard?

I'm working with some code that does a bunch of asynchronous operating with various callbacks; Snow Leopard has made this incredibly easy with blocks and GCD.
I'm calling NSTask from an NSBlockOperation like so:
[self.queue addOperationWithBlock:^{
NSTask *task = [NSTask new];
NSPipe *newPipe = [NSPipe new];
NSFileHandle *readHandle = [newPipe fileHandleForReading];
NSData *inData = nil;
[task setLaunchPath:path];
[task setArguments:arguments];
[task launch];
while ((inData = [readHandle availableData]) && [inData length]) {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// callback
}];
}
[task waitUntilExit];
}];
This approach works perfectly. It's like magic, as long as my callbacks handle the concurrency correctly.
Now, I want to be able to coalesce some of these calls; this is inside a model object's "refresh" method and may take a long time to complete. Having the user pound on the refresh button shouldn't tie up the machine and all that.
I can see an implementation dilemma here. I can make a whole bunch of queues - one per call type - and set their concurrent operation counts to 1 and then call -cancelAllOperations whenever it's time for a new call.
Alternately, I could do some more manual bookkeeping on which calls are currently happening and manage a single queue per model object (as I'm doing) or I could go even further and use a global queue.
How heavy is NSOperationQueue? Is creating a lot of queues a bad architecture decision? Is there a better way to coalesce these tasks?
If you're concerned about performance, don't guess: measure and then fix any bottlenecks you find. Adding queues is simple; try it and see what Instruments tells you about the effect on performance.
The main reason for creating multiple queues is in case you have some reason for wanting to start and stop them. If you just want to get the benefits of libdispatch, you can get that by just adding operations to the main queue.
You can add multiple blocks
to an NSBlockOperation which will be executed concurrently and can be canceled by
canceling the containing operation. As long as your individual tasks don't have to be serialized, this may work.
Just use as many operation queues as you like. They are here to separate logical parts of your program. I don't think you should be too concerned about the performance as long as you aren't allocating hundreds of queues per second.