web-dev-qa-db-fra.com

Exécuter une commande de terminal à partir d'une application Cocoa

Comment exécuter une commande de terminal (telle que grep) à partir de mon application Objective-C Cocoa?

196
lostInTransit

Vous pouvez utiliser NSTask. Voici un exemple qui exécuterait '/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 et NSFileHandle servent à rediriger la sortie standard de la tâche. 

Pour plus d'informations sur l'interaction avec le système d'exploitation depuis votre application Objective-C, consultez ce document sur le centre de développement Apple: Interaction avec le système d'exploitation

Edit: correctif inclus pour le problème NSLog

Si vous utilisez NSTask pour exécuter un utilitaire de ligne de commande via bash, vous devez inclure cette ligne magique pour que NSLog fonctionne:

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

Une explication est ici: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask

280
Gordon Wilson

dans l’esprit du partage ... c’est une méthode que j’utilise fréquemment pour exécuter des scripts Shell . vous pouvez ajouter un script à votre ensemble de produits (dans la phase de copie de la construction) puis lire et exécuter à l'exécution. Remarque: ce code recherche le script dans le sous-chemin privateFrameworks . warning: cela pourrait représenter un risque pour la sécurité des produits déployés, mais pour notre développement interne, il s'agit d'un moyen simple de personnaliser à rsync à ...) sans recompiler l'application, mais simplement en modifiant le script Shell de l'ensemble.

//------------------------------------------------------
-(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: correctif inclus pour le problème NSLog

Si vous utilisez NSTask pour exécuter un utilitaire de ligne de commande via bash, vous devez inclure cette ligne magique pour que NSLog fonctionne:

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

Dans le contexte:

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

Une explication est ici: http://www.cocoadev.com/index.pl?NSTask

40
kent

l'article de kent m'a donné une nouvelle idée. cette méthode runCommand n'a pas besoin d'un fichier de script mais exécute une commande par une ligne:

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

Vous pouvez utiliser cette méthode comme ceci:

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

Voici comment le faire dans Swift

Changements pour Swift 3.0:

  • NSPipe a été renommé Pipe

  • NSTask a été renommé Process


Ceci est basé sur la réponse Objective-C d’encre ci-dessus. Il l'a écrit sous la formecategoryon NSString - Pour Swift, il devient unextensionof String.

extension String.runAsCommand () -> Chaîne

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

Usage:

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

ou juste:

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

Exemple:

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

}

Notez que le résultat Process lu à partir de Pipe est un objet NSString. Il peut s'agir d'une chaîne d'erreur et d'une chaîne vide, mais il devrait toujours s'agir d'une NSString.

Donc, tant que ce n'est pas nul, le résultat peut être converti en un Swift String et être retourné.

Si, pour une raison quelconque, aucune variable NSString ne peut être initialisée à partir des données du fichier, la fonction renvoie un message d'erreur. La fonction aurait pu être écrite pour renvoyer un String? optionnel, mais ce serait difficile à utiliser et ne servirait à rien car il est si peu probable que cela se produise.

24
ElmerCat

Objective-C (voir ci-dessous pour Swift)

Nettoyé le code dans la première réponse pour le rendre plus lisible, moins redondant, ajouté les avantages de la méthode à une ligne et transformé en une catégorie NSString

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

La mise en oeuvre:

@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

Usage:

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

Et si vous rencontrez des problèmes d’encodage de sortie:

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

J'espère que c'est aussi utile pour vous que pour l'avenir. (Salut toi!)


Swift 4

Voici un exemple rapide utilisant Pipe, Process et 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)
    }
}

Usage:

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

fork , exec , et wait devrait fonctionner, si vous ne recherchez pas vraiment un moyen spécifique à Objective-C. fork crée une copie du programme en cours d'exécution, exec remplace le programme en cours d'exécution par un nouveau programme et wait attend que le sous-processus se termine. Par exemple (sans vérification d'erreur):

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

Il y a aussi system , qui exécute la commande comme si vous l'aviez saisie à partir de la ligne de commande du shell. C'est plus simple, mais vous avez moins de contrôle sur la situation.

Je suppose que vous travaillez sur une application Mac. Les liens renvoient à la documentation d'Apple pour ces fonctions, mais ils sont tous POSIX et vous devriez donc les utiliser sur tout système compatible POSIX.

14
Zach Hirsch

Il y a aussi le bon vieux POSIX system ("echo -en '\ 007'");

11
nes1983

J'ai écrit cette fonction "C", parce que NSTask est odieux ..

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, et pour être complet/sans ambiguïté…

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

Des années plus tard, C est toujours un désordre ahurissant pour moi .. et avec peu de foi en ma capacité à corriger mes lacunes flagrantes ci-dessus - le seul rameau d'olivier que je propose est une version avec rezhuzhed de la réponse de @ inket qui est à peu de chose , pour mes compagnons puristes/haïsseurs de verbosité ...

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 a déclaré:

Je suis surpris que personne ne se soit véritablement intéressé au blocage ou au blocage des problèmes d'appels 

Pour les appels bloquants ou non bloquants concernant NSTask, lisez ci-dessous:

asynctask.m - exemple de code montrant comment implémenter des flux asynchrones stdin, stdout et stderr pour le traitement de données avec NSTask

Le code source de asynctask.m est disponible sur GitHub .

3
jon

Si la commande Terminal requiert le privilège d'administrateur (également appelé Sudo), utilisez plutôt AuthorizationExecuteWithPrivileges à la place de . Ce qui suit créera un fichier nommé "com.stackoverflow.test" qui est le répertoire racine "/ 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

Ou puisque Objective C est juste C avec un calque OO en haut, vous pouvez utiliser les composants 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[]); 

Ils sont inclus dans le fichier d'en-tête unistd.h.

2
Paulo Lopes

Outre les excellentes réponses ci-dessus, j'utilise le code suivant pour traiter la sortie de la commande en arrière-plan et éviter le mécanisme de blocage de [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