Jak mogę wykonać polecenie terminalu (jak grep
) z mojej aplikacji Objective-C Cocoa?
/usr/bin
miejsce grep
zamieszkania.
Jak mogę wykonać polecenie terminalu (jak grep
) z mojej aplikacji Objective-C Cocoa?
/usr/bin
miejsce grep
zamieszkania.
Odpowiedzi:
Możesz użyć NSTask
. Oto przykład, który działałby „ /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
i NSFileHandle
służą do przekierowania standardowego wyjścia zadania.
Bardziej szczegółowe informacje na temat interakcji z systemem operacyjnym z poziomu aplikacji Objective-C można znaleźć w tym dokumencie w Centrum programistycznym Apple: Interakcja z systemem operacyjnym .
Edycja: Dołączona poprawka dla problemu NSLog
Jeśli używasz NSTask do uruchamiania narzędzia wiersza poleceń poprzez bash, musisz dołączyć tę magiczną linię, aby NSLog działał:
//The magic line that keeps your log where it belongs
task.standardOutput = pipe;
Wyjaśnienie znajduje się tutaj: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
NSMutableData *data = [NSMutableData dataWithCapacity:512];
. Potem while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
. I „wierzę”, że powinieneś mieć jeszcze jeden [data appendData:[file readDataToEndOfFile]];
po wyjściu z pętli while.
task.standardError = pipe;
Artykuł Kent dał mi nowy pomysł. ta metoda runCommand nie potrzebuje pliku skryptu, po prostu uruchamia polecenie za pomocą wiersza:
- (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;
}
Możesz użyć tej metody w następujący sposób:
NSString *output = runCommand(@"ps -A | grep mysql");
w duchu udostępniania ... jest to metoda, której często używam do uruchamiania skryptów powłoki. możesz dodać skrypt do pakietu produktu (w fazie kopiowania kompilacji), a następnie odczytać skrypt i uruchomić go w czasie wykonywania. Uwaga: ten kod szuka skryptu w ścieżce podrzędnej privateFrameworks. ostrzeżenie: może to stanowić zagrożenie bezpieczeństwa dla wdrożonych produktów, ale dla naszego wewnętrznego rozwoju jest to łatwy sposób dostosowywania prostych rzeczy (takich jak host do rsync do ...) bez ponownej kompilacji aplikacji, ale po prostu edytując skrypt powłoki w pakiecie.
//------------------------------------------------------
-(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);
}
//------------------------------------------------------
Edycja: Dołączona poprawka dla problemu NSLog
Jeśli używasz NSTask do uruchamiania narzędzia wiersza poleceń poprzez bash, musisz dołączyć tę magiczną linię, aby NSLog działał:
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
W kontekście:
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Wyjaśnienie znajduje się tutaj: http://www.cocoadev.com/index.pl?NSTask
Zmiany w Swift 3.0:
NSPipe
został przemianowanyPipe
NSTask
został przemianowanyProcess
Jest to oparte na powyższej odpowiedzi Inkit na Objective-C. Pisał go jako kategorii sprawie NSString
- do szybkiego, staje się rozszerzenie z 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"
Lub tylko:
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()
}
Zwróć uwagę, że Process
wynik odczytany z Pipe
jest NSString
obiektem. Może to być ciąg błędu i może to być również ciąg pusty, ale zawsze powinien to być ciąg NSString
.
Tak więc, dopóki nie jest zero, wynik może być rzucony jako Szybki String
i powrócił.
Jeśli z jakiegoś powodu NSString
w ogóle nie można zainicjować z danych pliku, funkcja zwraca komunikat o błędzie. Funkcja mogła zostać napisana w celu zwrócenia opcjonalnego String?
, ale byłoby to niewygodne w użyciu i nie służyłoby pożytecznemu celowi, ponieważ tak mało prawdopodobne jest, aby tak się stało.
Wyczyściłem kod w górnej odpowiedzi, aby był bardziej czytelny, mniej zbędny, dodał zalety metody jednowierszowej i przekształcił się w kategorię NSString
@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end
Realizacja:
@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
Stosowanie:
NSString* output = [@"echo hello" runAsCommand];
A jeśli masz problemy z kodowaniem wyjściowym:
// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
Mam nadzieję, że będzie to dla ciebie równie przydatne, jak dla mnie przyszłości. (Witam Cię!)
Oto przykład użycie Swift making of Pipe
, Process
iString
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)
}
}
Stosowanie:
let output = "echo hello".run()
fork , exec i wait powinny działać, jeśli tak naprawdę nie szukasz sposobu specyficznego dla Objective-C. fork
tworzy kopię aktualnie działającego programu, exec
zastępuje aktualnie działający program nowym i wait
czeka na zakończenie podprocesu. Na przykład (bez sprawdzania błędów):
#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. */
Istnieje również system , który uruchamia polecenie tak, jakbyś wpisał je z wiersza poleceń powłoki. To prostsze, ale masz mniejszą kontrolę nad sytuacją.
Zakładam, że pracujesz nad aplikacją na komputery Mac, więc linki prowadzą do dokumentacji Apple dla tych funkcji, ale są one wszystkie POSIX
, więc powinieneś używać ich w dowolnym systemie zgodnym z POSIX.
Istnieje również stary, dobry system POSIX („echo -en '\ 007'”);
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Napisałem tę funkcję „C”, ponieważ NSTask
jest wstrętna ..
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 ..
…
och, a dla zachowania kompletności / jednoznaczności…
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Wiele lat później C
dla mnie nadal jest oszałamiający bałagan… i z niewielką wiarą w moją zdolność do skorygowania moich rażących niedociągnięć powyżej - jedyną gałązką oliwną, którą oferuję, jest zmieniona wersja odpowiedzi @ inket, która jest najzupełniej kośćca , dla moich kolegów purystów / nienawistnych hejterów ...
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 powiedział:
Dziwi mnie, że nikt tak naprawdę nie interesował się problemami z blokowaniem / nieblokowaniem połączeń
W przypadku problemów związanych z blokowaniem / nieblokowaniem połączeń, o których NSTask
mowa poniżej:
asynctask.m - przykładowy kod pokazujący, jak zaimplementować asynchroniczne strumienie stdin, stdout i stderr do przetwarzania danych za pomocą NSTask
Kod źródłowy asynctask.m jest dostępny na GitHub .
Oprócz kilku doskonałych odpowiedzi powyżej, używam następującego kodu do przetwarzania danych wyjściowych polecenia w tle i unikania mechanizmu blokowania [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];
}
Lub ponieważ celem C jest po prostu C z warstwą OO na górze, możesz użyć elementów 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[]);
Są one zawarte w pliku nagłówkowym unistd.h.
Jeśli polecenie Terminal wymaga uprawnień administratora (alias sudo
), użyj AuthorizationExecuteWithPrivileges
zamiast tego. Poniżej utworzy się plik o nazwie „com.stackoverflow.test” to katalog główny „/ 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);