it-swarm.com.ru

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

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

196
lostInTransit

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

280
Gordon Wilson

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

Правка: Включено исправление для проблемы 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

40
kent

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

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

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

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

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


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

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

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

24
ElmerCat

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

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

// 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()
15
inket

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

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

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

14
Zach Hirsch

Есть также старый добрый POSIX system ("echo -en '\ 007'");

11
nes1983

Я написал эту функцию "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 для меня все еще был непонятным беспорядком ... и с малой верой в мою способность исправить мои грубые недостатки выше - единственная оливковая ветвь, которую я предлагаю, это rezhuzhed версия ответа @ inket, которая является barest of кости, для моих собратьев-пуристов/многословных ненавистников ...

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

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

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

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

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

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

3
jon

Если для команды «Терминал» требуются права администратора (он же 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); 
2
SwiftArchitect

Или, поскольку Objective C - это просто C с некоторым слоем OO сверху, вы можете использовать составные части 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.

2
Paulo Lopes

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