Wie kann ich einen Terminalbefehl (wie grep
) in meiner Objective-C Cocoa-Anwendung ausführen ?
/usr/bin
denen sie grep
leben.
Wie kann ich einen Terminalbefehl (wie grep
) in meiner Objective-C Cocoa-Anwendung ausführen ?
/usr/bin
denen sie grep
leben.
Antworten:
Sie können verwenden NSTask
. 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 Aufgabe umzuleiten.
Ausführlichere Informationen zur Interaktion mit dem Betriebssystem in Ihrer Objective-C-Anwendung finden Sie in diesem Dokument im Apple Development Center: Interaktion mit dem Betriebssystem .
Bearbeiten: Enthaltene Korrektur für NSLog-Problem
Wenn Sie NSTask verwenden, um ein Befehlszeilenprogramm über Bash auszuführen, müssen Sie diese magische Zeile einfügen, damit NSLog 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
NSMutableData *data = [NSMutableData dataWithCapacity:512];
. Dann while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
. Und ich "glaube", dass Sie [data appendData:[file readDataToEndOfFile]];
nach dem Beenden der while-Schleife noch eine haben sollten .
task.standardError = pipe;
Kents Artikel brachte mich auf eine neue Idee. Diese runCommand-Methode benötigt keine Skriptdatei, sondern führt nur einen Befehl 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");
im Geiste des Teilens ... ist dies eine Methode, die ich häufig verwende, um Shell-Skripte auszuführen. Sie können Ihrem Produktpaket ein Skript hinzufügen (in der Kopierphase des Builds) und das Skript dann zur Laufzeit lesen und ausführen lassen. Hinweis: Dieser Code sucht nach dem Skript im Unterpfad privateFrameworks. Warnung: Dies könnte ein Sicherheitsrisiko für bereitgestellte Produkte darstellen. Für unsere interne Entwicklung ist es jedoch eine einfache Möglichkeit, einfache Dinge (z. B. den Host, auf den synchronisiert werden soll ...) anzupassen, ohne die Anwendung neu zu kompilieren, sondern nur die zu bearbeiten Shell-Skript im 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);
}
//------------------------------------------------------
Bearbeiten: Enthaltene Korrektur für NSLog-Problem
Wenn Sie NSTask verwenden, um ein Befehlszeilenprogramm über Bash auszuführen, müssen Sie diese magische Zeile einfügen, damit NSLog 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
Änderungen für Swift 3.0:
NSPipe
wurde umbenanntPipe
NSTask
wurde umbenanntProcess
Dies basiert auf der obigen Objective-C-Antwort von inkit. Er schrieb es als Kategorie auf NSString
- Für Swift wird es eine Erweiterung von 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"
oder nur:
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()
}
Beachten Sie, dass das Process
ausgelesene Ergebnis Pipe
ein NSString
Objekt ist. Es kann eine Fehlerzeichenfolge sein und es kann auch eine leere Zeichenfolge sein, aber es sollte immer eine sein NSString
.
Solange es nicht Null ist, kann das Ergebnis als Swift gewertet String
und zurückgegeben werden.
Wenn aus irgendeinem Grund überhaupt nichts NSString
aus den Dateidaten initialisiert werden kann, gibt die Funktion eine Fehlermeldung zurück. Die Funktion hätte geschrieben werden können, um ein optionales Element zurückzugeben String?
, aber das wäre umständlich zu verwenden und würde keinen nützlichen Zweck erfüllen, da dies so unwahrscheinlich ist.
Der Code in der oberen Antwort wurde bereinigt, um ihn lesbarer und weniger redundant zu machen. Außerdem wurden die Vorteile der einzeiligen Methode hinzugefügt und wurde zu einer NSString-Kategorie
@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
Verwendung:
NSString* output = [@"echo hello" runAsCommand];
Und wenn Sie Probleme mit der Ausgabecodierung haben:
// 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 von mir. (Hallo Du!)
Hier ist ein Swift Beispiel macht Verwendung von Pipe
, Process
undString
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)
}
}
Verwendung:
let output = "echo hello".run()
Fork , Exec und Wait sollten funktionieren, wenn Sie nicht wirklich nach einem Objective-C-spezifischen Weg suchen. fork
Erstellt eine Kopie des aktuell ausgeführten Programms, exec
ersetzt das aktuell ausgeführte 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 ein System , das den Befehl so ausführt, als hätten Sie ihn über die Befehlszeile der Shell eingegeben. Es ist einfacher, aber Sie haben weniger Kontrolle über die Situation.
Ich gehe davon aus, dass Sie an einer Mac-Anwendung arbeiten. Die Links beziehen sich also auf die Apple-Dokumentation für diese Funktionen, aber sie sind alle vorhanden POSIX
. Sie sollten sie daher auf jedem POSIX-kompatiblen System verwenden.
Es gibt auch ein gutes altes POSIX- System ("echo -en '\ 007'");
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Ich habe diese "C" -Funktion geschrieben, weil sie NSTask
widerlich 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 C
ist es für mich immer noch ein verwirrendes Durcheinander ... und mit wenig Vertrauen in meine Fähigkeit, meine oben genannten groben Mängel zu korrigieren - der einzige Olivenzweig, den ich anbiete, ist eine rezhuzhed Version von @ inkets Antwort , die für meinen Kollegen kaum Knochen enthält Puristen / Ausführlichkeitshasser ...
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 sagte:
Ich bin überrascht, dass niemand wirklich Probleme mit blockierenden / nicht blockierenden Anrufen hatte
Bei blockierenden / nicht blockierenden Anrufproblemen NSTask
lesen Sie bitte Folgendes:
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 bei GitHub verfügbar .
Zusätzlich zu den oben genannten hervorragenden Antworten verwende ich den folgenden Code, um die Ausgabe des Befehls im Hintergrund zu verarbeiten und den Blockierungsmechanismus von zu vermeiden [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];
}
Oder da Ziel C nur C mit einer OO-Schicht darüber ist, können Sie die Posix-Gegenstücke 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 sind in der Header-Datei unistd.h enthalten.
Wenn für den Terminal-Befehl Administratorrechte (auch bekannt als Administratorrechte sudo
) erforderlich sind , verwenden Sie AuthorizationExecuteWithPrivileges
stattdessen. Im Folgenden wird eine Datei mit dem Namen "com.stackoverflow.test" erstellt. Dies ist das Stammverzeichnis "/ 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);