Выполнить команду терминала из приложения Какао

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

Я просто заявляю очевидное: с песочницей вы не можете просто запускать приложения, которых нет в вашей песочнице, И они должны быть подписаны вами, чтобы разрешить это

Daij-Djan 27.08.2015 12:05

@ Daij-Djan, это совсем не так, по крайней мере, в macOS. Приложение macOS в песочнице может запускать любой из двоичных файлов в таких местах, как /usr/bin, где находится grep.

jeff-h 28.06.2018 11:02

Нет. Пожалуйста, докажите, что я ошибаюсь;) на ist nstask не сможет запустить ничего, кроме вашей песочницы.

Daij-Djan 28.06.2018 16:00
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
206
3
126 386
12
Перейти к ответу Данный вопрос помечен как решенный

Ответы 12

Ответ принят как подходящий

Вы можете использовать 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);

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

Большое спасибо. Если мне нужно добавить какие-либо параметры, такие как -e, могу ли я также добавить их в массив аргументов?

lostInTransit 05.01.2009 11:44

Ага, 'arguments = [NSArray arrayWithObjects: @ "- e", @ "foo", @ "bar.txt", nil];'

Gordon Wilson 05.01.2009 11:47

Только не забудьте завершить вызов arrayWithObjects нулем :)

Jason Coco 05.01.2009 12:12

В вашем ответе есть небольшая ошибка. NSPipe имеет буфер (установленный на уровне ОС), который очищается при чтении. Если буфер заполнится, NSTask зависнет, и ваше приложение тоже будет зависать на неопределенный срок. Сообщение об ошибке не появится. Это может произойти, если NSTask возвращает много информации. Решение - использовать NSMutableData *data = [NSMutableData dataWithCapacity:512];. Затем while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }. И я «верю», что у вас должен быть еще один [data appendData:[file readDataToEndOfFile]]; после выхода из цикла while.

Dave 28.09.2011 02:49

Ошибки не возникнут, если вы не сделаете этого (они просто будут напечатаны в журнале): [task setStandardError: pipe];

Mike Sprague 08.08.2012 02:32

Комментарий Дэйва хороший. Однако вы можете использовать availableData вместо readDataToEndOfFile, потому что availableData будет блокироваться при отсутствии данных. Важно, если выполняемая команда потребляет много ресурсов ЦП и дает мало результатов, например gcc.

digory doo 01.08.2013 19:22

Можно ли использовать это в режиме песочницы? Потому что у меня это сработало. Но когда я включил режим песочницы, на выходе я получаю пустую строку. Кто-нибудь может мне помочь?

Suran 22.09.2013 09:19

Это можно обновить с помощью ARC и литералов массива Obj-C. Например. pastebin.com/sRvs3CqD

bames53 26.09.2013 03:39

Также рекомендуется передавать ошибки по конвейеру. task.standardError = pipe;

vqdave 21.10.2014 03:44

http 404 на сайте cocoadev.com ссылка :(

GBF_Gabriel 18.02.2015 19:23

Вот кешированная версия этой ссылки: web.archive.org/web/20141121094204/https://cocoadev.com/…

Daniel 26.09.2017 10:11

Или, поскольку Objective C - это просто C с некоторым уровнем объектно-ориентированного программирования наверху, вы можете использовать компоненты posix:

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

Они включены из файла заголовка unistd.h.

вилка, exec и ждать должны работать, если вы действительно не ищете способ, специфичный для 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-совместимой системе.

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

Nicos Karalis 18.02.2013 01:26

Есть еще старый добрый POSIX система ("echo -en '\ 007'");

НЕ ВЫПОЛНЯЙТЕ ЭТУ КОМАНДУ. (Если вы не знаете, что делает эта команда)

justin 09.12.2009 02:18

Поменял на что-то более безопасное… (пищит)

nes1983 09.12.2009 02:23

Разве это не вызовет ошибку в консоли? Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.

cwd 17.12.2011 22:47

Хм. Возможно, вам нужно дважды избежать обратной косой черты.

nes1983 17.12.2011 23:37

просто запустите / usr / bin / echo или что-то в этом роде. rm -rf жестко, а юникод в консоли все равно отстой :)

Maxim Veksler 03.01.2017 18:57

в духе совместного использования ... это метод, который я часто использую для запуска сценариев оболочки. вы можете добавить скрипт в пакет вашего продукта (на этапе копирования сборки), а затем пусть сценарий будет прочитан и запущен во время выполнения. примечание: этот код ищет сценарий в подпутье 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

Ссылка на объяснение мертва.

Jonny 08.07.2014 08:06

Я хочу запустить эту команду «system_profiler SPApplicationsDataType -xml», но получаю сообщение об ошибке «путь запуска недоступен»

Vikas Bansal 29.07.2015 15:20

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

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

Информацию о проблемах с блокировкой / неблокирующими вызовами относительно NSTask читайте ниже:

asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask

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

См. Мой вклад для неблокирующей версии

Guruniverse 03.05.2019 12:42

Если для команды терминала требуются права администратора (также известный как sudo), используйте вместо этого AuthorizationExecuteWithPrivileges. Следующее создаст файл с именем «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); 

Это официально не рекомендуется с OS X 10.7.

Sam Washburn 16.12.2012 13:58

... но он продолжает работать, несмотря на это, поскольку это единственный способ сделать это, и я считаю, что многие установщики полагаются на это.

Thomas Tempelmann 15.12.2016 14:34

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

Это хорошо обрабатывает большинство случаев, но если вы запустите его в цикле, это в конечном итоге вызовет исключение из-за слишком большого количества дескрипторов открытых файлов. Можно исправить, добавив: [file closeFile]; после readDataToEndOfFile.

David Stein 15.05.2016 22:54

@DavidStein: Я думаю, что использование autoreleasepool для обертывания метода runCommand кажется более подходящим, чем. На самом деле, приведенный выше код также не рассматривает не-ARC.

Kenial 17.05.2016 03:39

@Kenial: О, это гораздо лучшее решение. Он также высвобождает ресурсы сразу после выхода из области видимости.

David Stein 19.05.2016 10:02

/ bin / ps: Операция запрещена, у меня ничего не получается, свинец?

Naman Vaishnav 23.02.2018 15:32

Я написал эту функцию "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]; });
}

outP не определен при любой ошибке, chars_read слишком мал для возвращаемого значения fread () в любой архитектуре, где sizeof (ssize_t)! = sizeof (int), что, если нам нужно больше вывода, чем байтов BUFSIZ? Что делать, если вывод не UTF-8? Что делать, если pclose () возвращает ошибку? Как сообщить об ошибке fread ()?

ObjectiveC-oder 11.03.2014 14:45

@ ObjectiveC-oder D'oh - я не знаю. Пожалуйста, скажите мне (как в .. отредактировать)!

Alex Gray 02.12.2014 04:30

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

Действительно, ваш код мне очень пригодился! Я изменил его на Swift и опубликовал как еще один ответ ниже.

ElmerCat 27.08.2015 06:14

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

Changes for Swift 3.0:

  • NSPipe has been renamed Pipe

  • NSTask has been renamed Process


Это основано на ответе Inkit Objective-C выше. Он написал это как категория на NSString - Для Swift он становится расширение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 ---"
        }
    }
}

Применение:

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?, но это было бы неудобно использовать и бесполезно, потому что это маловероятно.

Действительно красивый и элегантный образ! Этот ответ должен иметь больше голосов.

XueYu 06.02.2017 06:17

Если вам не нужен вывод. Добавьте аргумент @discardableResult перед методом runCommand или над ним. Это позволит вам вызывать метод, не помещая его в переменную.

Lloyd Keijzer 03.08.2019 08:21

пусть результат = String (байты: fileHandle.readDataToEndOfFile (), кодировка: String.Encoding.utf8) в порядке

cleexiang 07.10.2019 18:38

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

Для меня решающее значение имела строка [self performSelectorInBackground: @selector (collectTaskOutput :) withObject: file];

neowinston 01.05.2019 17:06

Другие вопросы по теме