Как я могу выполнить терминальную команду (например grep) из моего приложения Objective-C Cocoa?
/usr/binгде они grepживут.
Как я могу выполнить терминальную команду (например grep) из моего приложения Objective-C Cocoa?
/usr/binгде они grepживут.
Ответы:
Вы можете использовать NSTask. Вот пример, который будет запускаться ' /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и NSFileHandleиспользуются для перенаправления стандартного вывода задачи.
Более подробную информацию о взаимодействии с операционной системой из приложения Objective-C вы можете найти в этом документе в Центре разработки Apple: Взаимодействие с операционной системой. .
Изменить: Включено исправление для проблемы NSLog
Если вы используете NSTask для запуска утилиты командной строки через bash, то вам нужно включить эту магическую строку, чтобы NSLog работал:
//The magic line that keeps your log where it belongs
task.standardOutput = pipe;
Объяснение здесь: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
NSMutableData *data = [NSMutableData dataWithCapacity:512];. Тогда while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }. И я «верю», что у вас должен быть еще один [data appendData:[file readDataToEndOfFile]];после выхода из цикла while.
task.standardError = pipe;
Статья Кента дала мне новую идею. этот метод runCommand не нуждается в файле сценария, он просто запускает команду по строке:
- (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;
}
Вы можете использовать этот метод следующим образом:
NSString *output = runCommand(@"ps -A | grep mysql");
в духе обмена ... это метод, который я часто использую для запуска сценариев оболочки. Вы можете добавить сценарий в комплект вашего продукта (на этапе копирования сборки), а затем выполнить чтение и запуск сценария во время выполнения. примечание: этот код ищет скрипт в подпути privateFrameworks. предупреждение: это может быть угрозой безопасности для развернутых продуктов, но для нашей собственной разработки это простой способ настроить простые вещи (например, какой хост rsync для ...), не перекомпилируя приложение, а просто отредактировав скрипт оболочки в комплекте.
//------------------------------------------------------
-(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);
}
//------------------------------------------------------
Изменить: Включено исправление для проблемы NSLog
Если вы используете NSTask для запуска утилиты командной строки через bash, то вам нужно включить эту магическую строку, чтобы NSLog работал:
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
В контексте:
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Объяснение здесь: http://www.cocoadev.com/index.pl?NSTask
Изменения для Swift 3.0:
NSPipeбыл переименованPipe
NSTaskбыл переименованProcess
Это основано на ответе Obit-C от Inkit выше. Он написал ее в качестве категории по NSString- Для Swift, она становится продолжением из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"
или просто:
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()
}
Обратите внимание на Processрезультат , как считывается из Pipeявляется NSStringобъектом. Это может быть строка ошибки, а также пустая строка, но она всегда должна бытьNSString .
Так что, пока это не ноль, результат можно разыграть как Swift String и вернуть.
Если по каким-либо причинам NSStringинициализация вообще не может быть выполнена из данных файла, функция возвращает сообщение об ошибке. Эта функция могла бы быть написана так, чтобы она возвращала необязательный параметр String?, но это было бы неудобно для использования и не могло бы служить полезной цели, поскольку это вряд ли произойдет.
Очистили код в верхнем ответе, чтобы сделать его более читабельным, менее избыточным, добавили преимущества однострочного метода и превратили в категорию NSString
@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end
Реализация:
@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
Использование:
NSString* output = [@"echo hello" runAsCommand];
И если у вас есть проблемы с кодировкой вывода:
// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
Надеюсь, это так же полезно для вас, как и для меня в будущем. (Привет тебе!)
Вот Свифт использование пример изготовление Pipe, Processи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)
}
}
Использование:
let output = "echo hello".run()
fork , exec и wait должны работать, если вы на самом деле не ищете специфический для Objective-C способ. forkсоздает копию запущенной в данный момент программы, execзаменяет текущую запущенную программу новой и waitожидает завершения подпроцесса. Например (без проверки ошибок):
#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. */
Также есть система , которая запускает команду так, как будто вы набрали ее из командной строки оболочки. Это проще, но у вас меньше контроля над ситуацией.
Я предполагаю, что вы работаете над приложением Mac, поэтому ссылки приведены на документацию Apple по этим функциям, но они все POSIX, так что вы должны использовать их в любой POSIX-совместимой системе.
Существует также старая добросовестная система POSIX ("echo -en '\ 007'");
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Я написал эту функцию "C", потому что NSTaskэто неприятно ..
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 ..
…
о, и ради того, чтобы быть полным / однозначным ...
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Годы спустя, Cдля меня это все еще изумительный беспорядок ... и с малой верой в мою способность исправить мои грубые недостатки выше - единственная оливковая ветвь, которую я предлагаю, это rezhuzhed версия ответа @ inket, которая является чуть-чуть костей для моего товарища пуристы / многословные ненавистники ...
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]; });
}
Кастос Мортем сказал:
Я удивлен, что никто не сталкивался с проблемами блокировки / неблокирования вызовов.
Для блокирующих / неблокирующих проблем звонка относительно NSTaskпрочитанных ниже:
asynctask.m - пример кода, показывающий, как реализовать асинхронные потоки stdin, stdout и stderr для обработки данных с помощью NSTask
Исходный код asynctask.m доступен на GitHub .
В дополнение к нескольким превосходным ответам, приведенным выше, я использую следующий код, чтобы обработать вывод команды в фоновом режиме и избежать механизма блокировки [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];
}
Или, поскольку Objective C - это просто C с некоторым слоем OO сверху, вы можете использовать составные части 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[]);
Они включены в заголовочный файл unistd.h.
Если для команды «Терминал» требуется привилегия администратора (aka sudo), используйте AuthorizationExecuteWithPrivilegesвместо этого. Далее будет создан файл с именем «com.stackoverflow.test», являющийся корневым каталогом «/ 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);