Comment puis-je exécuter une commande de terminal (comme grep
) à partir de mon application Objective-C Cocoa?
/usr/bin
endroit où grep
vit.
Comment puis-je exécuter une commande de terminal (comme grep
) à partir de mon application Objective-C Cocoa?
/usr/bin
endroit où grep
vit.
Réponses:
Vous pouvez utiliser NSTask
. Voici un exemple qui fonctionnerait ' /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 à partir de votre application Objective-C, vous pouvez voir ce document sur le Centre de développement d'Apple: Interagir avec le système d'exploitation .
Edit: correction incluse 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
NSMutableData *data = [NSMutableData dataWithCapacity:512];
. Ensuite, while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
. Et je "crois" que vous devriez en avoir un de plus [data appendData:[file readDataToEndOfFile]];
après la sortie de la boucle while.
task.standardError = pipe;
L'article de Kent m'a donné une nouvelle idée. cette méthode runCommand n'a pas besoin d'un fichier de script, 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");
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 bundle de produits (dans la phase de copie de la build), puis faire lire et exécuter le script au moment de l'exécution. remarque: ce code recherche le script dans le sous-chemin privateFrameworks. avertissement: cela pourrait être un risque pour la sécurité des produits déployés, mais pour notre développement en interne, c'est un moyen facile de personnaliser des choses simples (comme l'hôte à rsync ...) sans recompiler l'application, mais simplement éditer le script shell dans le bundle.
//------------------------------------------------------
-(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: correction incluse 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
Changements pour Swift 3.0:
NSPipe
a été renomméPipe
NSTask
a été renomméProcess
Ceci est basé sur la réponse Objective-C de inkit ci-dessus. Il l'a écrit comme une catégorie sur NSString
- Pour Swift, cela devient une extension de 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 le Process
résultat tel qu'il est lu dans Pipe
est un NSString
objet. Il peut s'agir d'une chaîne d'erreur et peut également être une chaîne vide, mais elle doit toujours être un NSString
.
Donc, tant qu'il n'est pas nul, le résultat peut être converti en Swift String
et renvoyé.
Si, pour une raison quelconque, aucun ne NSString
peut être initialisé à partir des données du fichier, la fonction renvoie un message d'erreur. La fonction aurait pu être écrite pour renvoyer une option String?
, mais ce serait maladroit à utiliser et ne servirait à rien car il est 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 avec l'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 moi. (Salut toi!)
Voici un exemple de l' utilisation de la prise Swift Pipe
, Process
etString
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 devraient fonctionner si vous ne cherchez pas vraiment une méthode 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 et wait
attend la fin du sous-processus. 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 le système , qui exécute la commande comme si vous l'avez tapée à 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, donc les liens sont vers la documentation d'Apple pour ces fonctions, mais ils sont tous POSIX
, vous devriez donc les utiliser sur n'importe quel système compatible POSIX.
Il y a aussi un bon vieux système POSIX ("echo -en '\ 007'");
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
J'ai écrit cette fonction "C", car elle NSTask
est désagréable ..
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
c'est toujours un gâchis déconcertant, pour moi .. et avec peu de foi en ma capacité à corriger mes défauts grossiers ci-dessus - la seule branche d'olivier que j'offre est une version rezhuzhed de la réponse de @ inket qui est la plus fine des os , pour mon collègue puristes / haineux de la 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 n'ait vraiment eu de problèmes d'appels bloquants / non bloquants
Pour les problèmes d'appels bloquants / non bloquants concernant la NSTask
lecture ci-dessous:
asynctask.m - exemple de code qui montre comment implémenter des flux stdin, stdout et stderr asynchrones pour le traitement des données avec NSTask
Le code source de asynctask.m est disponible sur GitHub .
En plus des nombreuses 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]
.
- (void)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];
}
Ou puisque l'objectif C est juste C avec une couche OO sur le dessus, vous pouvez utiliser les contre-parties 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.
Si la commande Terminal Server requiert un privilège administrateur (aka sudo
), utilisez AuthorizationExecuteWithPrivileges
plutôt. Ce qui suit va créer 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);