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

Comment puis-je exécuter une commande de terminal (comme grep ) à partir de mon application Objectif-C Cocoa?

193
demandé sur Jonas 2009-01-05 11:21:32

11 réponses

vous pouvez utiliser NSTask . Voici un exemple qui s'exécute ' /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 sont utilisés pour 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, vous pouvez consulter ce document sur le centre de développement D'Apple: interaction avec le système d'exploitation .

Edit: Inclus correctif pour NSLog problème

si vous utilisez NSTask pour exécuter un utilitaire en ligne de commande via bash, alors vous devez inclure cette ligne magique pour garder NSLog fonctionnel:

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

277
répondu Gordon Wilson 2017-09-26 08:29:51

dans un esprit de partage... c'est une méthode que j'utilise fréquemment pour exécuter des scripts shell. vous pouvez ajouter un script à votre lot de produits (dans la phase de copie de la construction) et puis faire lire et exécuter le script à l'exécution. note: CE code recherche le script dans le sous-chemin privateFrameworks. avertissement: ce pourrait être un risque de sécurité pour les produits déployés, mais pour notre propre développement, c'est un moyen facile de personnaliser les choses simples (comme l'hôte de rsync...) sans re-compilation de l' application, mais il suffit d'éditer le script shell dans le paquet.

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

modifier: correction incluse pour le problème NSLog

si vous utilisez NSTask pour exécuter un utilitaire en ligne de commande via bash, alors vous devez inclure cette ligne magique pour garder NSLog fonctionnel:

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

Dans son 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
répondu kent 2010-03-20 11:19:57

l'article de kent m'a donné une nouvelle idée. cette méthode runCommand n'a pas besoin d'un fichier script, elle exécute simplement 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
répondu Kenial 2016-05-17 00:31:11

Voici comment le faire en Swift

changements pour Swift 3.0:

  • NSPipe a été renommé Pipe

  • NSTask a été renommé Process


cette réponse est basée sur la réponse Objective-C d'inkit ci-dessus. Il l'a écrit comme un catégorie sur NSString - Pour Swift, elle devient une extension de String .

chaîne d'extension.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 ---"
        }
    }
}

Utilisation:

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

ou simplement:

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

}

Note le Process résultat dans la version anglaise du Pipe est un objet NSString . Il peut s'agir d'une chaîne d'erreur et d'une chaîne vide, mais il doit toujours s'agir d'une NSString .

ainsi, tant qu'il n'est pas nul, le résultat peut être moulé comme un Swift String et retourné.

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

20
répondu ElmerCat 2016-09-26 00:34:49

fork , exec , et wait devrait fonctionner, si vous n'êtes pas vraiment à la recherche D'un objectif spécifique. fork crée une copie du programme en cours d'exécution, exec remplace le programme en cours d'exécution avec un nouveau, et wait attend le sous-processus de sortie. Par exemple (sans aucune 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 tapée depuis 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, donc les liens sont vers la documentation D'Apple pour ces fonctions, mais ils sont tous POSIX , donc vous devriez les utiliser sur N'importe quel système conforme à POSIX.

14
répondu Zach Hirsch 2017-01-03 16:35:03

objectif-C (Voir ci-dessous pour Swift)

a nettoyé le code dans la réponse supérieure pour le rendre plus lisible, moins redondant, a ajouté les avantages de la méthode d'une ligne et fait dans une catégorie NSString

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

mise en Œuvre:

@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

Utilisation:

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

et si vous avez des problèmes avec le codage 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 moi. (Hi, vous!)


Swift 4

voici un exemple rapide faisant usage de 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)
    }
}

Utilisation:

let output = "echo hello".run()
14
répondu inket 2017-06-20 06:36:50

Il ya aussi bon vieux POSIX système ("echo-en '\007'");

11
répondu nes1983 2013-05-25 13:15:03

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, '"151900920"', 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 par souci d'être complet / sans ambiguïté...

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

des années plus tard, C est toujours un gâchis déconcertant, pour moi.. et avec peu de foi en ma capacité à corriger mes graves défauts ci - dessus-la seule branche d'Olivier que j'offre est une version rezhuzhed de la réponse de @inket qui est le plus d'OS , pour mes collègues les puristes / verbosité-haters...

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
répondu Alex Gray 2015-04-01 08:54:16

Custos Mortem dit:

je suis surpris que personne n'ait vraiment eu de problèmes de blocage / non-blocage des appels

Pour le blocage/appel non bloquant les questions concernant les NSTask lire ci-dessous:

asynctask.m -- exemple de code qui montre comment mettre en œuvre des flux asynchrones stdin, stdout & stderr pour le traitement des données avec NSTask

code Source de asynctask.m est disponible au GitHub .

3
répondu jon 2014-06-06 08:25:34

ou comme L'objectif c est juste C avec une couche OO sur le dessus, vous pouvez utiliser les 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[]); 

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

2
répondu Paulo Lopes 2009-01-05 08:32:25

si la commande Terminal nécessite le privilège administrateur (alias sudo ), utilisez AuthorizationExecuteWithPrivileges à la place. Le suivant va créer un fichier nommé "com.stackoverflow.test "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
répondu SwiftArchitect 2016-12-17 05:14:14