Выполнить команду терминала из приложения Какао - PullRequest
198 голосов
/ 05 января 2009

Как я могу выполнить команду терминала (например, grep) из моего приложения Objective-C Cocoa?

Ответы [ 12 ]

282 голосов
/ 05 января 2009

Вы можете использовать <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSTask_Class/Reference/Reference.html" rel="noreferrer">NSTask</a>. Вот пример, который будет запускать '/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 и NSFileHandle используются для перенаправления стандартного вывода задачи.

Для получения более подробной информации о взаимодействии с операционной системой из приложения Objective C вы можете увидеть этот документ в Центре разработки Apple: Взаимодействие с операционной системой .

Редактировать: включено исправление для проблемы NSLog

Если вы используете NSTask для запуска утилиты командной строки через bash, то вам нужно включить эту магическую строку, чтобы NSLog работал:

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

Объяснение здесь: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

40 голосов
/ 30 марта 2009

в духе обмена ... это метод, который я часто использую для запуска сценариев оболочки. Вы можете добавить скрипт в комплект вашего продукта (на этапе копирования сборки), а затем чтобы скрипт был прочитан и запущен во время выполнения. примечание: этот код ищет сценарий в подпути privateFrameworks. предупреждение: это может быть угрозой безопасности для развернутых продуктов, но для нашей собственной разработки это простой способ настроить простые вещи (например, с какого хоста на rsync в ...), не перекомпилируя приложение, а просто редактируя скрипт оболочки в комплекте.

//------------------------------------------------------
-(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);    
}
//------------------------------------------------------

Редактировать: включено исправление для проблемы NSLog

Если вы используете NSTask для запуска утилиты командной строки через bash, то вам нужно включить эту магическую строку, чтобы NSLog работал:

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

В контексте:

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

Объяснение здесь: http://www.cocoadev.com/index.pl?NSTask

38 голосов
/ 07 сентября 2012

статья Кента дала мне новую идею. этот метод 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");
24 голосов
/ 27 августа 2015

Вот как это сделать в Swift

Изменения для Swift 3.0:

  • NSPipe было переименовано Pipe

  • NSTask был переименован Process


Это основано на ответе Inkit Objective-C от Inkit выше. Он написал это как категория на NSString - Для Swift он становится расширением из String.

расширение 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 ---"
        }
    }
}

Использование:

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

или просто:

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

Пример:

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

}

Обратите внимание, что результат Process, считанный с Pipe, является объектом NSString. Это может быть строка ошибки, а также пустая строка, но она всегда должна быть NSString.

Так что, пока он не равен нулю, результат можно разыграть как Swift String и вернуть.

Если по каким-либо причинам вообще нельзя инициализировать NSString из данных файла, функция возвращает сообщение об ошибке. Функция могла бы быть написана так, чтобы она возвращала необязательный String?, но это было бы неудобно для использования и не служило бы полезной цели, потому что это маловероятно для этого.

16 голосов
/ 26 сентября 2013

Objective-C (см. Ниже для Swift)

Очистил код в верхнем ответе, чтобы сделать его более читабельным, менее избыточным, добавил преимущества однострочный метод и превратил его в категорию NSString

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

Реализация:

@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

Использование:

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

И , если у вас проблемы с выходной кодировкой:

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

Надеюсь, это так же полезно для вас, как и для меня в будущем. (Привет, ты!)


Swift 4

Вот пример Swift, использующий Pipe, Process и 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)
    }
}

Использование:

let output = "echo hello".run()
14 голосов
/ 05 января 2009

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. */

Также есть система , которая запускает команду так, как будто вы набрали ее из командной строки оболочки. Это проще, но у вас меньше контроля над ситуацией.

Я предполагаю, что вы работаете над приложением Mac, поэтому ссылки приведены на документацию Apple по этим функциям, но все они POSIX, поэтому вам следует использовать их в любой POSIX-совместимой системе.

11 голосов
/ 13 января 2009

Существует также старая добрая система POSIX ("echo -en '\ 007'");

7 голосов
/ 25 мая 2013

Я написал эту функцию "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]; });
}
3 голосов
/ 28 января 2011

Кустос Мортем сказал:

Я удивлен, что никто на самом деле не сталкивался с проблемами блокировки / неблокирования вызовов

По вопросам блокировки / неблокирования вызовов, касающихся NSTask, читайте ниже:

asynctask.m - пример кода, показывающий, как реализовать асинхронные потоки stdin, stdout и stderr для обработки данных с помощью NSTask

Исходный код asynctask.m доступен на GitHub .

2 голосов
/ 18 февраля 2019

В дополнение к нескольким превосходным ответам, приведенным выше, я использую следующий код, чтобы обработать вывод команды в фоновом режиме и избежать механизма блокировки [file readDataToEndOfFile].

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

    [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];
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...