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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/412562/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 21:01:39  来源:igfitidea点击:

Execute a terminal command from a Cocoa app

objective-ccocoamacos

提问by lostInTransit

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

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

回答by Gordon Wilson

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

您可以使用NSTask. 这是一个将运行“ /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);

NSPipeand NSFileHandleare 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

回答by Kenial

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

回答by kent

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.

本着分享的精神……这是我经常用来运行 shell 脚本的一种方法。您可以将脚本添加到您的产品包中(在构建的复制阶段),然后在运行时读取和运行脚本。注意:此代码在 privateFrameworks 子路径中查找脚本。警告:这对于已部署的产品来说可能是一个安全风险,但对于我们的内部开发来说,这是一种无需重新编译应用程序而只需编辑包中的 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

回答by ElmerCat

Here's how to do it in Swift

以下是如何在 Swift 中执行此操作

Changes for Swift 3.0:

  • NSPipehas been renamed Pipe

  • NSTaskhas been renamed Process

Swift 3.0 的变化:

  • NSPipe已更名 Pipe

  • NSTask已更名 Process



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

这是基于上面 inkit 的 Objective-C 答案。他写了它作为一个类别NSString-对于斯威夫特,它成为一个扩展String

extension  String.runAsCommand()  ->  String

扩展 String.runAsCommand() -> 字符串

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 Processresult as read from the Pipeis an NSStringobject. It might be an error string and it can also be an empty string, but it should always be an NSString.

请注意ProcessPipeis读取的结果是一个NSString对象。它可能是一个错误字符串,也可能是一个空字符串,但它应该始终是一个NSString.

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

因此,只要它不是 nil,结果就可以转换为 SwiftString并返回。

If for some reason no NSStringat 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.

如果由于某种原因根本NSString无法从文件数据初始化,该函数将返回一条错误消息。该函数可以被编写为返回一个 optional String?,但使用起来会很尴尬并且不会起到有用的作用,因为这种情况发生的可能性很小。

回答by inket

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 methodand 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 ifyou'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

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

回答by Zach Hirsch

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

forkexecwait应该可以工作,如果你不是真的在寻找一种 Objective-C 特定的方式。fork创建当前运行程序的副本,exec用新程序替换当前运行的程序,然后wait等待子进程退出。例如(没有任何错误检查):

#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.

还有system,它运行命令就像您从 shell 的命令行键入它一样。它更简单,但您对情况的控制较少。

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 的系统上使用它们。

回答by nes1983

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

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

回答by Alex Gray

I wrote this "C" function, because NSTaskis obnoxious..

我写了这个“C”函数,因为它NSTask很讨厌..

NSString * runCommand(NSString* c) {

    NSString* outP; FILE *read_fp;  char buffer[BUFSIZ + 1];
    int chars_read; memset(buffer, '
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
', 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…

哦,为了完整/明确......

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

Years later, Cis 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 答案的 rezhuzhed 版本,对于我的同胞来说,这是赤裸裸的骨头纯粹主义者/讨厌冗长...

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

回答by jon

Custos Mortem said:

Custo Mortem 说:

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

我很惊讶没有人真正陷入阻塞/非阻塞呼叫问题

For blocking/non-blocking call issues regarding NSTaskread below:

对于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 上获得

回答by Guruniverse

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].

##代码##