简体   繁体   English

从Cocoa应用程序执行终端命令

[英]Execute a terminal command from a Cocoa app

如何从Objective-C Cocoa应用程序执行终端命令(如grep )?

You can use NSTask . 您可以使用NSTask Here's an example that would run ' /usr/bin/grep foo bar.txt '. 这是一个运行' /usr/bin/grep foo bar.txt '的/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. NSPipeNSFileHandle用于重定向任务的标准输出。

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 . 有关从Objective-C应用程序中与操作系统交互的更多详细信息,您可以在Apple的开发中心上看到此文档: 与操作系统交互

Edit: Included fix for NSLog problem 编辑:包含NSLog问题的修复程序

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: 如果您使用NSTask通过bash运行命令行实用程序,那么您需要包含此魔术行以保持NSLog正常工作:

//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 解释如下: https//web.archive.org/web/20141121094204/https//cocoadev.com/HowToPipeCommandsWithNSTask

in the spirit of sharing... this is a method I use frequently to run shell scripts. 本着共享的精神...这是我经常用来运行shell脚本的方法。 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. 注意:此代码在privateFrameworks子路径中查找脚本。 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. 警告:这可能会对部署的产品造成安全风险,但对于我们的内部开发,它是一种简单的方法来定制简单的东西(比如rsync到哪个主机......)而无需重新编译应用程序,只需编辑捆绑包中的shell脚本。

//------------------------------------------------------
-(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 编辑:包含NSLog问题的修复程序

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: 如果您使用NSTask通过bash运行命令行实用程序,那么您需要包含此魔术行以保持NSLog正常工作:

//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 解释如下: http//www.cocoadev.com/index.pl?NSTask

kent's article gave me a new idea. 肯特的文章给了我一个新的想法。 this runCommand method doesn't need a script file, just runs a command by a line: 这个runCommand方法不需要脚本文件,只需按行运行命令:

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

Here's how to do it in Swift 这是在Swift中如何做到这一点

Changes for Swift 3.0: Swift 3.0的变化:

  • NSPipe has been renamed Pipe NSPipe已更名为Pipe

  • NSTask has been renamed Process NSTask已重命名为Process


This is based on inkit's Objective-C answer above. 这是基于上面的墨水的Objective-C答案。 He wrote it as a category on NSString — For Swift, it becomes an extension of String . 他把它写成NSString上的一个 - 对于Swift,它成为String扩展

extension String.runAsCommand() -> 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. 请注意,从Pipe读取的Process结果是NSString对象。 It might be an error string and it can also be an empty string, but it should always be an NSString . 它可能是一个错误字符串,它也可以是一个空字符串,但它应该始终是一个NSString

So, as long as it's not nil, the result can cast as a Swift String and returned. 因此,只要它不是nil,结果就可以转换为Swift String并返回。

If for some reason no NSString at all can be initialized from the file data, the function returns an error message. 如果由于某种原因,根本无法从文件数据初始化NSString ,则该函数返回错误消息。 The function could have been written to return an optional String? 可以编写该函数以返回可选的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) Objective-C(参见下面的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 清除了最顶层答案中的代码,使其更具可读性,更少冗余,增加了单行方法的优点并制作成NSString类别

@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 斯威夫特4

Here's a Swift example making use of Pipe , Process , and String 这是一个使用PipeProcessString的Swift示例

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

fork , exec , and wait should work, if you're not really looking for a Objective-C specific way. forkexecwait应该工作,如果你不是真的在寻找Objective-C特定的方法。 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. fork创建当前正在运行的程序的副本, exec用一个新的替换当前正在运行的程序,并wait等待子进程退出。 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. 还有系统 ,它运行命令,就好像你从shell的命令行输入命令一样。 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. 我假设您正在使用Mac应用程序,因此这些链接是针对这些函数的Apple文档,但它们都是POSIX ,因此您应该在任何符合POSIX的系统上使用它们。

还有一个很好的旧POSIX 系统 (“echo -en'\\ 007'”);

I wrote this "C" function, because NSTask is obnoxious.. 我写了这个“C”函数,因为NSTask是令人讨厌的..

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... 多年以后, C对我来说仍然是一个令人眼花缭乱的混乱......并且对我纠正上述严重缺点的能力缺乏信心 - 我提供的唯一橄榄枝是@ inket的答案,是最简单的骨头 ,对于我而言同为纯粹主义者/冗长的人...

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]; });
}

Custos Mortem said: Custos Mortem说:

I'm surprised no one really got into blocking/non-blocking call issues 我很惊讶没有人真正陷入阻塞/非阻塞呼叫问题

For blocking/non-blocking call issues regarding NSTask read below: 有关NSTask阻塞/非阻塞调用问题, NSTask阅读以下内容:

asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask asynctask.m - 示例代码,演示如何使用NSTask实现异步stdin,stdout和stderr流以处理数据

Source code of asynctask.m is available at GitHub . asynctask.m的源代码可以在GitHub上找到

Or since Objective C is just C with some OO layer on top you can use the posix conterparts: 或者由于Objective C只是C,顶部有一些OO层,你可以使用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. 它们包含在unistd.h头文件中。

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] . 除了上面的几个优秀答案之外,我使用以下代码在后台处理命令的输出,并避免[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];
}

If the Terminal command requires Administrator Privilege (aka sudo ), use AuthorizationExecuteWithPrivileges instead. 如果终端命令需要管理员权限(也称为sudo ),请改用AuthorizationExecuteWithPrivileges The following will create a file named "com.stackoverflow.test" is the root directory "/System/Library/Caches". 下面将创建一个名为“com.stackoverflow.test”的文件,它是根目录“/ 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); 

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM