Comment exécuter une commande de terminal (telle que grep
) à partir de mon application Objective-C Cocoa?
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
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
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");
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 {
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"
ou juste:
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()
}
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.
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!)
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()
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.
Il y a aussi le bon vieux POSIX system ("echo -en '\ 007'");
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]; });
}
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 .
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);
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.
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];
}