it-swarm.com.de

Führen Sie einen Terminalbefehl aus einer Cocoa-App aus

Wie kann ich einen Terminalbefehl (wie grep) aus meiner Objective-C Cocoa-Anwendung ausführen?

196
lostInTransit

Sie können NSTask verwenden. Hier ist ein Beispiel, das "/usr/bin/grep foo bar.txt" ausführen würde. 

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 und NSFileHandle werden verwendet, um die Standardausgabe der Task umzuleiten. 

Ausführlichere Informationen zur Interaktion mit dem Betriebssystem innerhalb Ihrer Objective-C-Anwendung finden Sie im Apple Development Center in diesem Dokument: Interaktion mit dem Betriebssystem

Bearbeiten: Enthaltenes Update für NSLog-Problem

Wenn Sie NSTask verwenden, um ein Befehlszeilendienstprogramm über Bash auszuführen, müssen Sie diese magische Linie einfügen, damit NSLog weiterhin funktioniert:

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

Eine Erklärung finden Sie hier: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

280
Gordon Wilson

im Geiste der Freigabe ... Dies ist eine Methode, die ich häufig zum Ausführen von Shell-Skripts benutze. Sie können Ihrem Produkt-Bundle ein Skript hinzufügen (in der Kopierphase des Builds) und dann das Skript haben Lesen und zur Laufzeit ausführen. Hinweis: Dieser Code sucht nach dem Skript im privateFrameworks-Unterpfad . Warnung: Dies kann ein Sicherheitsrisiko für bereitgestellte Produkte sein. Für unsere Eigenentwicklung ist dies jedoch eine einfache Möglichkeit, einfache Dinge (wie zB den Host) anzupassen rsync to ...), ohne die Anwendung neu zu kompilieren, sondern nur das Shell-Skript im Bundle zu bearbeiten.

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

Bearbeiten: Enthaltenes Update für NSLog-Problem

Wenn Sie NSTask verwenden, um ein Befehlszeilendienstprogramm über Bash auszuführen, müssen Sie diese magische Linie einfügen, damit NSLog weiterhin funktioniert:

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

Im Zusammenhang:

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

Eine Erklärung finden Sie hier: http://www.cocoadev.com/index.pl?NSTask

40
kent

kents Artikel brachte mir eine neue Idee. Diese runCommand-Methode benötigt keine Skriptdatei, sondern führt einen Befehl nur in einer Zeile aus:

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

Sie können diese Methode folgendermaßen verwenden:

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

So geht's in Swift

Änderungen für Swift 3.0:

  • NSPipe wurde umbenannt in Pipe

  • NSTask wurde umbenannt in Process


Dies basiert auf der oben genannten Objective-C-Antwort von inkit. Er schrieb es alscategoryauf NSString - Für Swift wird es eineextensionvon String.

erweiterung 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 ---"
        }
    }
}

Verwendungszweck:

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

oder nur:

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

Beispiel:

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

}

Beachten Sie, dass das Process-Ergebnis, wie von der Pipe gelesen, ein NSString-Objekt ist. Es kann sich um eine Fehlerzeichenfolge handeln und es kann sich auch um eine leere Zeichenfolge handeln, es sollte jedoch immer eine NSString sein.

Solange dies nicht gleich Null ist, kann das Ergebnis als Swift String umgewandelt und zurückgegeben werden.

Wenn aus irgendeinem Grund keine NSString aus den Dateidaten initialisiert werden kann, gibt die Funktion eine Fehlermeldung zurück. Die Funktion könnte geschrieben worden sein, um einen optionalen String? zurückzugeben, aber das wäre umständlich zu verwenden und würde keinen nützlichen Zweck erfüllen, da dies so unwahrscheinlich ist.

24
ElmerCat

Ziel-C (siehe unten für Swift)

Bereinigt den Code in der obersten Antwort, um ihn lesbarer und weniger redundant zu machen, fügte er die Vorteile der der Einzeilenmethode hinzu und stellte sie in eine NSString-Kategorie ein

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

Implementierung:

@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

Verwendungszweck:

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

Und if Sie haben Probleme mit der Ausgabecodierung:

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

Ich hoffe, es ist für Sie genauso nützlich wie für die Zukunft. (Hallo Du!)


Schnell 4

Hier ist ein schnelles Beispiel, das Pipe, Process und String verwendet.

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

Verwendungszweck:

let output = "echo hello".run()
15
inket

fork , exec und wait sollte funktionieren, wenn Sie nicht wirklich nach einem Objective-C-spezifischen Weg suchen. fork erstellt eine Kopie des aktuell laufenden Programms, exec ersetzt das aktuell laufende Programm durch ein neues und wait wartet, bis der Unterprozess beendet ist. Zum Beispiel (ohne Fehlerprüfung):

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

Es gibt auch system , das den Befehl so ausführt, als ob Sie ihn über die Befehlszeile der Shell eingegeben hätten. Es ist einfacher, aber Sie haben weniger Kontrolle über die Situation.

Ich gehe davon aus, dass Sie an einer Mac-Anwendung arbeiten. Die Links verweisen auf die Dokumentation von Apple für diese Funktionen. Sie sind jedoch alle POSIX. Sie sollten sie daher auf jedem POSIX-kompatiblen System verwenden.

14
Zach Hirsch

Es gibt auch gutes altes POSIX system ("echo -en '\ 007'");

11
nes1983

Ich habe diese "C" -Funktion geschrieben, weil NSTask unangenehm ist.

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 ..
…

oh und um vollständig/eindeutig zu sein ...

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

Jahre später ist C immer noch ein verwirrendes Durcheinander für mich .. und mit wenig Vertrauen in meine Fähigkeit, meine groben Mängel oben zu korrigieren - der einzige Olivenzweig, den ich anbiete, ist eine rezhuzhierte Version von @inket's Antwort, die knappste Knochen) ist, für meine Mit-Puristen/Verbosity-Hasser ...

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

Custos Mortem hat gesagt:

Ich bin überrascht, dass niemand wirklich Probleme mit dem Blockieren/Nicht-Blockieren von Anrufen hatte 

Zum Blockieren/Nicht-Blockieren von Anrufproblemen bezüglich NSTask lesen Sie unten:

asynctask.m - Beispielcode, der zeigt, wie asynchrone Stdin-, Stdout- und Stderr-Streams für die Datenverarbeitung mit NSTask implementiert werden

Der Quellcode von asynctask.m ist unter GitHub verfügbar.

3
jon

Wenn für den Terminalbefehl Administratorrechte (aka Sudo) erforderlich sind, verwenden Sie stattdessen AuthorizationExecuteWithPrivileges . Im Folgenden wird eine Datei mit dem Namen "com.stackoverflow.test" erstellt, in der sich das Stammverzeichnis "/ System/Library/Caches" befindet.

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

Oder da Objective C nur C ist und einige OO - Ebenen darüber liegen, können Sie die posix-Conterparts verwenden:

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

Sie werden aus der Header-Datei unistd.h eingefügt.

2
Paulo Lopes

Zusätzlich zu den verschiedenen hervorragenden Antworten verwende ich den folgenden Code, um die Ausgabe des Befehls im Hintergrund zu verarbeiten und den Blockierungsmechanismus von [file readDataToEndOfFile] zu vermeiden.

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