Wykonaj polecenie terminal z aplikacji Cocoa

Jak mogę wykonać polecenie terminal (jak grep) z mojej aplikacji Objective-C Cocoa?

Author: Jonas, 2009-01-05

11 answers

Możesz użyć NSTask. Oto przykład, który będzie dział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 i NSFileHandle są używane do przekierowania standardowego wyjścia zadania.

Więcej szczegółowych informacji na temat interakcji z systemem operacyjnym z poziomu aplikacji Objective-C można znaleźć w tym dokumencie w centrum rozwoju firmy Apple: interakcja z systemem operacyjnym .

Edit: dołączona poprawka dla problemu NSLog

Jeśli używasz NSTask do uruchomienia wiersza poleceń narzędzie poprzez bash, następnie musisz dołączyć tę magiczną linię, aby nslog działał:

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

Wyjaśnienie jest tutaj: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

 277
Author: Gordon Wilson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-26 08:29:51

W duchu dzielenia się... jest to metoda, której często używam do uruchamiania skryptów powłoki. możesz dodać skrypt do swojego pakietu produktu (w fazie kopiowania kompilacji), a następnie niech skrypt zostanie odczytany i uruchomiony w czasie wykonywania. uwaga: ten kod szuka skryptu w podścieżce privateFrameworks. ostrzeżenie: może to stanowić zagrożenie dla bezpieczeństwa wdrożonych produktów, ale dla naszego rozwoju wewnętrznego jest to łatwy sposób na dostosowanie prostych rzeczy (takich jak host do rsync...) bez ponownej kompilacji aplikacji, ale tylko edycja skryptu powłoki w pakiecie.

//------------------------------------------------------
-(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: dołączona poprawka dla problemu NSLog

Jeśli używasz NSTask do uruchamiania narzędzia wiersza poleceń za pomocą bash, musisz dołączyć tę magiczną linię, aby nslog działał:

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

W kontekście:

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

Wyjaśnienie jest tutaj: http://www.cocoadev.com/index.pl?NSTask

 40
Author: kent,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-20 11:19:57

Artykuł Kenta podsunął mi nowy pomysł. ta metoda runCommand nie wymaga pliku skryptu, po prostu uruchamia polecenie w linii:

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

Możesz użyć tej metody w następujący sposób:

NSString *output = runCommand(@"ps -A | grep mysql");
 38
Author: Kenial,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-17 00:31:11

Oto Jak to zrobić w języku Swift

Zmiany dla Swift 3.0:

  • NSPipe został przemianowany Pipe

  • NSTask został przemianowany Process


Jest to oparte na odpowiedzi Objective-C firmy inkit powyżej. Napisał go jako Kategoria na NSString - Dla Swift, staje się rozszerzenie Z String.

Ciąg Rozszerzeń.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 ---"
        }
    }
}

Użycie:

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

Lub po prostu:

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

Przykład:

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

}

Zwróć uwagę, że Process wynik odczytany z Pipe jest obiektem NSString. Może to być łańcuch błędów i może to być również pusty łańcuch, ale zawsze powinien to być NSString.

Tak długo, jak nie jest zero, wynik może rzucić jako Swift {[9] } i wrócić.

Jeśli z jakiegoś powodu nie NSString w ogóle może być zainicjowana z pliku data funkcja zwraca komunikat o błędzie. Funkcja mogła zostać napisana tak, aby zwracała opcjonalne String?, ale byłoby to niewygodne w użyciu i nie służyłoby użytecznemu celowi, ponieważ jest tak mało prawdopodobne, aby do tego doszło.

 20
Author: ElmerCat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-26 00:34:49

Widelec, exec i wait powinny działać, jeśli nie szukasz sposobu określonego w Objective-C. fork tworzy kopię aktualnie uruchomionego programu, exec zastępuje aktualnie uruchomiony program nowym i wait czeka na zakończenie podprocesu. Na przykład (bez sprawdzania błędów):

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

Istnieje również system , który uruchamia polecenie tak, jakbyś wpisał je z linii poleceń powłoki. To prostsze, ale masz mniejszą kontrolę nad sytuacją.

Zakładam, że pracujesz nad aplikacją Mac, więc linki są do dokumentacji Apple dla tych funkcji, ale wszystkie są POSIX, więc powinieneś używać ich na dowolnym systemie zgodnym z POSIX.

 14
Author: Zach Hirsch,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-03 16:35:03

Objective-C (patrz niżej Swift)

Wyczyściłem kod w górnej odpowiedzi, aby był bardziej czytelny, mniej zbędny, dodałem zalety metody jednolinijkowej i przekształciłem w kategorię NSString

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

Realizacja:

@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

Użycie:

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

I jeśli masz problemy z kodowaniem wyjściowym:

// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
Mam nadzieję, że będzie dla ciebie tak samo przydatna, jak dla mnie. Cześć!)

Swift 4

Oto szybki przykład wykorzystania Pipe, Process, i 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)
    }
}

Użycie:

let output = "echo hello".run()
 14
Author: inket,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-20 06:36:50

Istnieje również stary, dobry system POSIX ("echo-PL '\007'");

 11
Author: nes1983,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-25 13:15:03

Napisałem tę funkcję "C", ponieważ NSTask jest wstrętna..

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 ..
…
I ze względu na to, że jest kompletna / jednoznaczna ... ]}
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Lata później, wciąż jest dla mnie oszałamiającym bałaganem.. i z niewielką wiarą w moją zdolność do korygowania moich rażących niedociągnięć powyżej - jedyną gałązką oliwną oferuję jest rezhuzhed Wersja odpowiedzi @inket, która jest barest of bones , dla moich kolegów purystów / werbosity-hejterów...
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
Author: Alex Gray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-01 08:54:16

Custos Mortem said:

Jestem zaskoczony, że nikt tak naprawdę nie dostał się do blokowania / non-blocking problemy połączeń

W przypadku problemów z blokowaniem / nieblokowaniem połączeń dotyczących NSTask przeczytaj poniżej:

Asynctask.m -- przykładowy kod pokazujący jak zaimplementować asynchroniczne strumienie stdin, stdout i stderr do przetwarzania danych za pomocą NSTask

Kod źródłowy asynctask.m jest dostępny na GitHub .

 3
Author: jon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-06 08:25:34

Lub ponieważ Objective C jest po prostu C z jakąś warstwą OO na górze, możesz użyć conterparts 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[]); 

Są one zawarte z unistd.plik nagłówkowy H.

 2
Author: Paulo Lopes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-05 08:32:25

Jeśli polecenie Terminal wymaga uprawnień administratora (aka sudo), Użyj AuthorizationExecuteWithPrivileges. Poniżej zostanie utworzony plik o nazwie "com.stackoverflow.test "jest katalogiem głównym" / 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
Author: SwiftArchitect,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-17 05:14:14