211

How can I execute a terminal command (like grep) from my Objective-C Cocoa application?

Jonas
  • 121,568
  • 97
  • 310
  • 388
lostInTransit
  • 70,519
  • 61
  • 198
  • 274
  • 2
    Im just stating the obvious: with sandboxing you can't just go start apps that are not in your sandbox AND they need to be signed by you to allow this – Daij-Djan Aug 27 '15 at 09:05
  • 1
    @Daij-Djan that's not true at all, at least not in macOS. A sandboxed macOS app can run any of the binaries in places such as `/usr/bin` where `grep` lives. – jeff-h Jun 28 '18 at 08:02
  • 1
    No. Please prove me wrong ;) on ist nstask will fail to run anything not in your sandbox. – Daij-Djan Jun 28 '18 at 13:00

12 Answers12

289

You can use NSTask. Here's an example that would run '/usr/bin/grep foo bar.txt'.

int pid = [[NSProcessInfo processInfo] processIdentifier];
NSPipe *pipe = [NSPipe pipe];
NSFileHandle *file = pipe.fileHandleForReading;

NSTask *task = [[NSTask alloc] init];
task.launchPath = @"/usr/bin/grep";
task.arguments = @[@"foo", @"bar.txt"];
task.standardOutput = pipe;

[task launch];

NSData *data = [file readDataToEndOfFile];
[file closeFile];

NSString *grepOutput = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
NSLog (@"grep returned:\n%@", grepOutput);

NSPipe and NSFileHandle are used to redirect the standard output of the task.

For more detailed information on interacting with the operating system from within your Objective-C application, you can see this document on Apple's Development Center: Interacting with the Operating System.

Edit: Included fix for NSLog problem

If you are using NSTask to run a command-line utility via bash, then you need to include this magic line to keep NSLog working:

//The magic line that keeps your log where it belongs
task.standardOutput = pipe;

An explanation is here: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

Daniel
  • 984
  • 8
  • 14
Gordon Wilson
  • 26,244
  • 11
  • 57
  • 60
  • Thanks a lot. If I need to add any options like -e, do I add them to the arguments array as well? – lostInTransit Jan 05 '09 at 08:44
  • 1
    Yup, 'arguments = [NSArray arrayWithObjects: @"-e", @"foo", @"bar.txt", nil];' – Gordon Wilson Jan 05 '09 at 08:47
  • Just don't forget to terminate the arrayWithObjects call with a nil :) – Jason Coco Jan 05 '09 at 09:12
  • 16
    There's a small glitch in your answer. NSPipe has a buffer (set at the OS level), which is flushed when it's read. If the buffer fills up, NSTask will hang, and your app will hang too, indefinitely. No error message will appear. This can happen if the NSTask returns a lot of info. The solution is to use `NSMutableData *data = [NSMutableData dataWithCapacity:512];`. Then, `while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }`. And I "believe" you should have one more `[data appendData:[file readDataToEndOfFile]];` after the while-loop exits. – Dave Sep 27 '11 at 22:49
  • 3
    Errors won't come up unless you do this (they just get printed in the log): [task setStandardError:pipe]; – Mike Sprague Aug 07 '12 at 22:32
  • 1
    Dave's comment is a good one. However, you might want to use availableData instead of readDataToEndOfFile, because availableData will block when there's no data. Important if the command that is being executed consumes much CPU and results in little output, such as gcc. – digory doo Aug 01 '13 at 15:22
  • Is it possible to use this in sandbox mode? Because this was working fine for me. But, when I enabled sandbox mode I'm getting an empty string as output. Can some one please help me? – Suran Sep 22 '13 at 05:19
  • 1
    This could be updated with ARC and with Obj-C array literals. E.g. http://pastebin.com/sRvs3CqD – bames53 Sep 25 '13 at 23:39
  • 1
    It's also a good idea to pipe the errors. `task.standardError = pipe;` – vqdave Oct 20 '14 at 23:44
  • 1
    http 404 on cocoadev.com link :( – GBF_Gabriel Feb 18 '15 at 16:23
  • Here's a cached version of that link: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask – Daniel Sep 26 '17 at 07:11
  • `pid` is unused in the example. – orion elenzil Aug 14 '23 at 22:42
46

kent's article gave me a new idea. this runCommand method doesn't need a script file, just runs a command by a line:

- (NSString *)runCommand:(NSString *)commandToRun
{
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];

    NSArray *arguments = [NSArray arrayWithObjects:
                          @"-c" ,
                          [NSString stringWithFormat:@"%@", commandToRun],
                          nil];
    NSLog(@"run command:%@", commandToRun);
    [task setArguments:arguments];

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

    NSFileHandle *file = [pipe fileHandleForReading];

    [task launch];

    NSData *data = [file readDataToEndOfFile];

    NSString *output = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    return output;
}

You can use this method like this:

NSString *output = runCommand(@"ps -A | grep mysql");
Kenial
  • 2,510
  • 23
  • 26
  • 1
    This handles most cases well, but if you run it in a loop, it eventually raises an exception due to too many open file handles. Can be fixed by adding: [file closeFile]; after readDataToEndOfFile. – David Stein May 15 '16 at 19:54
  • @DavidStein : I think using autoreleasepool to wrap runCommand method seems to be rather than. Actually, above code doesn't consider non-ARC as well. – Kenial May 17 '16 at 00:39
  • @Kenial: Oh, that's a much better solution. It also releases the resources promptly upon leaving the scope. – David Stein May 19 '16 at 07:02
  • /bin/ps: Operation not permitted , i'm not getting any success, lead ? – Naman Vaishnav Feb 23 '18 at 12:32
40

in the spirit of sharing... this is a method I use frequently to run shell scripts. you can add a script to your product bundle (in the copy phase of the build) and then have the script be read and run at runtime. note: this code looks for the script in the privateFrameworks sub-path. warning: this could be a security risk for deployed products, but for our in-house development it is an easy way to customize simple things (like which host to rsync to...) without re-compiling the application, but just editing the shell script in the bundle.

//------------------------------------------------------
-(void) runScript:(NSString*)scriptName
{
    NSTask *task;
    task = [[NSTask alloc] init];
    [task setLaunchPath: @"/bin/sh"];

    NSArray *arguments;
    NSString* newpath = [NSString stringWithFormat:@"%@/%@",[[NSBundle mainBundle] privateFrameworksPath], scriptName];
    NSLog(@"shell script path: %@",newpath);
    arguments = [NSArray arrayWithObjects:newpath, 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 (@"script returned:\n%@", string);    
}
//------------------------------------------------------

Edit: Included fix for NSLog problem

If you are using NSTask to run a command-line utility via bash, then you need to include this magic line to keep NSLog working:

//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];

In context:

NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];

An explanation is here: http://www.cocoadev.com/index.pl?NSTask

Steve McLeod
  • 51,737
  • 47
  • 128
  • 184
kent
  • 6,286
  • 4
  • 27
  • 32
27

Here's how to do it in Swift

Changes for Swift 3.0:

  • NSPipe has been renamed Pipe

  • NSTask has been renamed Process


This is based on inkit's Objective-C answer above. He wrote it as a category on NSString — For Swift, it becomes an extension of String.

extension  String.runAsCommand()  ->  String

extension String {
    func runAsCommand() -> String {
        let pipe = Pipe()
        let task = Process()
        task.launchPath = "/bin/sh"
        task.arguments = ["-c", String(format:"%@", self)]
        task.standardOutput = pipe
        let file = pipe.fileHandleForReading
        task.launch()
        if let result = NSString(data: file.readDataToEndOfFile(), encoding: String.Encoding.utf8.rawValue) {
            return result as String
        }
        else {
            return "--- Error running command - Unable to initialize string from file data ---"
        }
    }
}

Usage:

let input = "echo hello"
let output = input.runAsCommand()
print(output)                        // prints "hello"

    or just:

print("echo hello".runAsCommand())   // prints "hello" 

Example:

@IBAction func toggleFinderShowAllFiles(_ sender: AnyObject) {

    var newSetting = ""
    let readDefaultsCommand = "defaults read com.apple.finder AppleShowAllFiles"

    let oldSetting = readDefaultsCommand.runAsCommand()

    // Note: the Command results are terminated with a newline character

    if (oldSetting == "0\n") { newSetting = "1" }
    else { newSetting = "0" }

    let writeDefaultsCommand = "defaults write com.apple.finder AppleShowAllFiles \(newSetting) ; killall Finder"

    _ = writeDefaultsCommand.runAsCommand()

}

Note the Process result as read from the Pipe is an NSString object. It might be an error string and it can also be an empty string, but it should always be an NSString.

So, as long as it's not nil, the result can cast as a Swift String and returned.

If for some reason no NSString at all can be initialized from the file data, the function returns an error message. The function could have been written to return an optional String?, but that would be awkward to use and wouldn't serve a useful purpose because it's so unlikely for this to occur.

ElmerCat
  • 3,126
  • 1
  • 26
  • 34
  • 1
    Really Nice and Elegant way! This answer Should have more upvotes. – XueYu Feb 06 '17 at 03:17
  • If you don’t need the output. Add the @discardableResult argument infront or above the runCommand method. This will let you call the method without having to put it in a variable. – Lloyd Keijzer Aug 03 '19 at 05:21
  • let result = String(bytes: fileHandle.readDataToEndOfFile(), encoding: String.Encoding.utf8) is ok – cleexiang Oct 07 '19 at 15:38
21

Objective-C (see below for Swift)

Cleaned up the code in the top answer to make it more readable, less redundant, added the benefits of the one-line method and made into an NSString category

@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end

Implementation:

@implementation NSString (ShellExecution)

- (NSString*)runAsCommand {
    NSPipe* pipe = [NSPipe pipe];

    NSTask* task = [[NSTask alloc] init];
    [task setLaunchPath: @"/bin/sh"];
    [task setArguments:@[@"-c", [NSString stringWithFormat:@"%@", self]]];
    [task setStandardOutput:pipe];

    NSFileHandle* file = [pipe fileHandleForReading];
    [task launch];

    return [[NSString alloc] initWithData:[file readDataToEndOfFile] encoding:NSUTF8StringEncoding];
}

@end

Usage:

NSString* output = [@"echo hello" runAsCommand];

And if you're having problems with output encoding:

// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];

Hope it's as useful to you as it will be to future me. (Hi, you!)


Swift 4

Here's a Swift example making use of Pipe, Process, and String

extension String {
    func run() -> String? {
        let pipe = Pipe()
        let process = Process()
        process.launchPath = "/bin/sh"
        process.arguments = ["-c", self]
        process.standardOutput = pipe

        let fileHandle = pipe.fileHandleForReading
        process.launch()

        return String(data: fileHandle.readDataToEndOfFile(), encoding: .utf8)
    }
}

Usage:

let output = "echo hello".run()
inket
  • 1,641
  • 16
  • 21
  • 2
    Indeed, your code was very useful to me! I changed it to Swift and posted it as another answer below. – ElmerCat Aug 27 '15 at 03:14
15

fork, exec, and wait should work, if you're not really looking for a Objective-C specific way. fork creates a copy of the currently running program, exec replaces the currently running program with a new one, and wait waits for the subprocess to exit. For example (without any error checking):

#include <stdlib.h>
#include <unistd.h>


pid_t p = fork();
if (p == 0) {
    /* fork returns 0 in the child process. */
    execl("/other/program/to/run", "/other/program/to/run", "foo", NULL);
} else {
    /* fork returns the child's PID in the parent. */
    int status;
    wait(&status);
    /* The child has exited, and status contains the way it exited. */
}

/* The child has run and exited by the time execution gets to here. */

There's also system, which runs the command as if you typed it from the shell's command line. It's simpler, but you have less control over the situation.

I'm assuming you're working on a Mac application, so the links are to Apple's documentation for these functions, but they're all POSIX, so you should be to use them on any POSIX-compliant system.

Maxim Veksler
  • 29,272
  • 38
  • 131
  • 151
Zach Hirsch
  • 24,631
  • 8
  • 32
  • 29
  • I know this is an very old answer but i need to say this: this is an excelent way to use trheads to handle the excecution. the only downside is that it creates a copy of the entire program. so for a cocoa application i would go with @GordonWilson for a nicer aproach, and if i'm working on a command line application this is the best way to do it. thanks (sorry my bad english) – Nicos Karalis Feb 17 '13 at 21:26
11

There is also good old POSIX system("echo -en '\007'");

nes1983
  • 15,209
  • 4
  • 44
  • 64
9

I wrote this "C" function, because NSTask is obnoxious..

NSString * runCommand(NSString* c) {

    NSString* outP; FILE *read_fp;  char buffer[BUFSIZ + 1];
    int chars_read; memset(buffer, '\0', sizeof(buffer));
    read_fp = popen(c.UTF8String, "r");
    if (read_fp != NULL) {
        chars_read = fread(buffer, sizeof(char), BUFSIZ, read_fp);
        if (chars_read > 0) outP = $UTF8(buffer);
        pclose(read_fp);
    }   
    return outP;
}

NSLog(@"%@", runCommand(@"ls -la /")); 

total 16751
drwxrwxr-x+ 60 root        wheel     2108 May 24 15:19 .
drwxrwxr-x+ 60 root        wheel     2108 May 24 15:19 ..
…

oh, and for the sake of being complete / unambiguous…

#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])

Years later, C is still a bewildering mess, to me.. and with little faith in my ability to correct my gross shortcomings above - the only olive branch I offer is a rezhuzhed version of @inket's answer that is barest of bones, for my fellow purists / verbosity-haters...

id _system(id cmd) { 
   return !cmd ? nil : ({ NSPipe* pipe; NSTask * task;
  [task = NSTask.new setValuesForKeysWithDictionary: 
    @{ @"launchPath" : @"/bin/sh", 
        @"arguments" : @[@"-c", cmd],
   @"standardOutput" : pipe = NSPipe.pipe}]; [task launch];
  [NSString.alloc initWithData:
     pipe.fileHandleForReading.readDataToEndOfFile
                      encoding:NSUTF8StringEncoding]; });
}
Alex Gray
  • 16,007
  • 9
  • 96
  • 118
  • 1
    outP is undefined on any error, chars_read is too small for the return value of fread() on any architecture where sizeof(ssize_t) != sizeof(int), what if we want more output than BUFSIZ bytes? What if the output isn't UTF-8? What if pclose() returns an error? How do we report the error of fread()? – ObjectiveC-oder Mar 11 '14 at 10:45
  • @ObjectiveC-oder D'oh - I dunno. Please, tell me (as in.. edit away)! – Alex Gray Dec 02 '14 at 01:30
5

In addition to the several excellent answers above, I use the following code to process the output of the command in the background and avoid the blocking mechanism of [file readDataToEndOfFile].

- (void)runCommand:(NSString *)commandToRun
{
    NSTask *task = [[NSTask alloc] init];
    [task setLaunchPath:@"/bin/sh"];

    NSArray *arguments = [NSArray arrayWithObjects:
                          @"-c" ,
                          [NSString stringWithFormat:@"%@", commandToRun],
                          nil];
    NSLog(@"run command:%@", commandToRun);
    [task setArguments:arguments];

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

    NSFileHandle *file = [pipe fileHandleForReading];

    [task launch];

    [self performSelectorInBackground:@selector(collectTaskOutput:) withObject:file];
}

- (void)collectTaskOutput:(NSFileHandle *)file
{
    NSData      *data;
    do
    {
        data = [file availableData];
        NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] );

    } while ([data length] > 0); // [file availableData] Returns empty data when the pipe was closed

    // Task has stopped
    [file closeFile];
}
Guruniverse
  • 196
  • 2
  • 6
3

Custos Mortem said:

I'm surprised no one really got into blocking/non-blocking call issues

For blocking/non-blocking call issues regarding NSTask read below:

asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask

Source code of asynctask.m is available at GitHub.

Marshall Eubanks
  • 265
  • 2
  • 14
jon
  • 31
  • 1
  • See my [contribution](https://stackoverflow.com/questions/412562/execute-a-terminal-command-from-a-cocoa-app/54742163#54742163) for a non-blocking version – Guruniverse May 03 '19 at 09:42
2

Or since Objective C is just C with some OO layer on top you can use the posix conterparts:

int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
int execv(const char *path, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]); 

They are included from unistd.h header file.

Paulo Lopes
  • 5,845
  • 22
  • 31
2

If the Terminal command requires Administrator Privilege (aka sudo), use AuthorizationExecuteWithPrivileges instead. The following will create a file named "com.stackoverflow.test" is the root directory "/System/Library/Caches".

AuthorizationRef authorizationRef;
FILE *pipe = NULL;
OSStatus err = AuthorizationCreate(nil,
                                   kAuthorizationEmptyEnvironment,
                                   kAuthorizationFlagDefaults,
                                   &authorizationRef);

char *command= "/usr/bin/touch";
char *args[] = {"/System/Library/Caches/com.stackoverflow.test", nil};

err = AuthorizationExecuteWithPrivileges(authorizationRef,
                                         command,
                                         kAuthorizationFlagDefaults,
                                         args,
                                         &pipe); 
SwiftArchitect
  • 47,376
  • 28
  • 140
  • 179