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


当前回答

fork, exec和wait应该有用,如果你不是在寻找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. */

还有一个系统,它运行命令,就像您从shell的命令行输入命令一样。这样更简单,但你对情况的控制力更弱。

我假设你正在使用一个Mac应用程序,所以这些链接指向苹果关于这些函数的文档,但它们都是POSIX,所以你应该在任何POSIX兼容的系统上使用它们。

其他回答

fork, exec和wait应该有用,如果你不是在寻找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. */

还有一个系统,它运行命令,就像您从shell的命令行输入命令一样。这样更简单,但你对情况的控制力更弱。

我假设你正在使用一个Mac应用程序,所以这些链接指向苹果关于这些函数的文档,但它们都是POSIX,所以你应该在任何POSIX兼容的系统上使用它们。

验尸官说:

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

关于NSTask的阻塞/非阻塞调用问题,请阅读以下内容:

asynctask。m——示例代码,展示了如何实现异步stdin, stdout和stderr流处理数据与NSTask

asynctask的源代码。m可以在GitHub上找到。

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

我写了这个“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 ..
…

哦,为了完整/明确起见……

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

多年以后,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]; });
}

肯特的文章给了我一个新想法。这个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;
}

你可以这样使用这个方法:

NSString *output = runCommand(@"ps -A | grep mysql");