Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Execute a terminal command from a Cocoa app

People also ask

How do I run a command in Terminal?

To execute commands: Type the command in the Terminal prompt. Press Enter to execute it.

How do I run a Mac Terminal code?

In the Terminal app on your Mac, press the Up Arrow key. The last command you entered appears on the command line. Continue pressing the Up Arrow key until you see the command you want, then press Return.

What is command line code?

The command line (aka Terminal or Command Prompt) refers to a type of program that comes preinstalled with Windows, Linux and Mac computers and allows you to execute commands, run programs and navigate through the folders on your computer.

What is command line Mac?

The Mac Terminal is a command line interface (CLI) for the macOS operating system (OS). Mac Terminal is typically used by network administrators and advanced technical users who want to initiate an action that is not supported by the operating system's graphical user interface (GUI).


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


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");

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


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.


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()