Wykonaj polecenie terminal z aplikacji Cocoa
Jak mogę wykonać polecenie terminal (jak grep
) z mojej aplikacji Objective-C Cocoa?
11 answers
Możesz użyć NSTask
. Oto przykład, który będzie działał "/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żywane do przekierowania standardowego wyjścia zadania.
Więcej szczegółowych informacji na temat interakcji z systemem operacyjnym z poziomu aplikacji Objective-C można znaleźć w tym dokumencie w centrum rozwoju firmy Apple: interakcja z systemem operacyjnym .
Edit: dołączona poprawka dla problemu NSLog
Jeśli używasz NSTask do uruchomienia wiersza poleceń narzędzie poprzez bash, następnie 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 jest tutaj: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-26 08:29:51
W duchu dzielenia się... jest to metoda, której często używam do uruchamiania skryptów powłoki. możesz dodać skrypt do swojego pakietu produktu (w fazie kopiowania kompilacji), a następnie niech skrypt zostanie odczytany i uruchomiony w czasie wykonywania. uwaga: ten kod szuka skryptu w podścieżce privateFrameworks. ostrzeżenie: może to stanowić zagrożenie dla bezpieczeństwa wdrożonych produktów, ale dla naszego rozwoju wewnętrznego jest to łatwy sposób na dostosowanie prostych rzeczy (takich jak host do rsync...) bez ponownej kompilacji aplikacji, ale tylko edycja skryptu 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);
}
//------------------------------------------------------
Edit: dołączona poprawka dla problemu NSLog
Jeśli używasz NSTask do uruchamiania narzędzia wiersza poleceń za pomocą 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 jest tutaj: http://www.cocoadev.com/index.pl?NSTask
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-20 11:19:57
Artykuł Kenta podsunął mi nowy pomysł. ta metoda runCommand nie wymaga pliku skryptu, po prostu uruchamia polecenie w linii:
- (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");
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-05-17 00:31:11
Oto Jak to zrobić w języku Swift
Zmiany dla Swift 3.0:
NSPipe
został przemianowanyPipe
NSTask
został przemianowanyProcess
Jest to oparte na odpowiedzi Objective-C firmy inkit powyżej. Napisał go jako Kategoria na NSString
-
Dla Swift, staje się rozszerzenie Z String
.
Ciąg Rozszerzeń.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 ---"
}
}
}
Użycie:
let input = "echo hello"
let output = input.runAsCommand()
print(output) // prints "hello"
Lub po prostu:
print("echo hello".runAsCommand()) // prints "hello"
Przykład:
@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 obiektem NSString
. Może to być łańcuch błędów i może to być również pusty łańcuch, ale zawsze powinien to być NSString
.
Tak długo, jak nie jest zero, wynik może rzucić jako Swift {[9] } i wrócić.
Jeśli z jakiegoś powodu nie NSString
w ogóle może być zainicjowana z pliku data funkcja zwraca komunikat o błędzie. Funkcja mogła zostać napisana tak, aby zwracała opcjonalne String?
, ale byłoby to niewygodne w użyciu i nie służyłoby użytecznemu celowi, ponieważ jest tak mało prawdopodobne, aby do tego doszło.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-26 00:34:49
Widelec, exec i wait powinny działać, jeśli nie szukasz sposobu określonego w Objective-C. fork
tworzy kopię aktualnie uruchomionego programu, exec
zastępuje aktualnie uruchomiony 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 linii poleceń powłoki. To prostsze, ale masz mniejszą kontrolę nad sytuacją.
Zakładam, że pracujesz nad aplikacją Mac, więc linki są do dokumentacji Apple dla tych funkcji, ale wszystkie są POSIX
, więc powinieneś używać ich na dowolnym systemie zgodnym z POSIX.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-03 16:35:03
Objective-C (patrz niżej Swift)
Wyczyściłem kod w górnej odpowiedzi, aby był bardziej czytelny, mniej zbędny, dodałem zalety metody jednolinijkowej i przekształciłem 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
Użycie:
NSString* output = [@"echo hello" runAsCommand];
I 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 dla ciebie tak samo przydatna, jak dla mnie. Cześć!)
Swift 4
Oto szybki przykład wykorzystania Pipe
, Process
, i 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)
}
}
Użycie:
let output = "echo hello".run()
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-20 06:36:50
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-25 13:15:03
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 ..
…
I ze względu na to, że jest kompletna / jednoznaczna ... ]}
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Lata później, wciąż jest dla mnie oszałamiającym bałaganem.. i z niewielką wiarą w moją zdolność do korygowania moich rażących niedociągnięć powyżej - jedyną gałązką oliwną oferuję jest rezhuzhed Wersja odpowiedzi @inket, która jest barest of bones , dla moich kolegów purystów / werbosity-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]; });
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-01 08:54:16
Custos Mortem said:
Jestem zaskoczony, że nikt tak naprawdę nie dostał się do blokowania / non-blocking problemy połączeń
W przypadku problemów z blokowaniem / nieblokowaniem połączeń dotyczących NSTask
przeczytaj 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 .
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-06 08:25:34
Lub ponieważ Objective C jest po prostu C z jakąś warstwą OO na górze, możesz użyć 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[]);
Są one zawarte z unistd.plik nagłówkowy H.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-01-05 08:32:25
Jeśli polecenie Terminal wymaga uprawnień administratora (aka sudo
), Użyj AuthorizationExecuteWithPrivileges
.
Poniżej zostanie utworzony plik o nazwie "com.stackoverflow.test "jest katalogiem głównym" / 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);
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-17 05:14:14