Как я могу выполнить команду терминала (например, grep) из моего приложения Objective-C Cocoa?
@ Daij-Djan, это совсем не так, по крайней мере, в macOS. Приложение macOS в песочнице может запускать любой из двоичных файлов в таких местах, как /usr/bin, где находится grep.
Нет. Пожалуйста, докажите, что я ошибаюсь;) на ist nstask не сможет запустить ничего, кроме вашей песочницы.





Вы можете использовать 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
Большое спасибо. Если мне нужно добавить какие-либо параметры, такие как -e, могу ли я также добавить их в массив аргументов?
Ага, 'arguments = [NSArray arrayWithObjects: @ "- e", @ "foo", @ "bar.txt", nil];'
Только не забудьте завершить вызов arrayWithObjects нулем :)
В вашем ответе есть небольшая ошибка. NSPipe имеет буфер (установленный на уровне ОС), который очищается при чтении. Если буфер заполнится, NSTask зависнет, и ваше приложение тоже будет зависать на неопределенный срок. Сообщение об ошибке не появится. Это может произойти, если NSTask возвращает много информации. Решение - использовать NSMutableData *data = [NSMutableData dataWithCapacity:512];. Затем while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }. И я «верю», что у вас должен быть еще один [data appendData:[file readDataToEndOfFile]]; после выхода из цикла while.
Ошибки не возникнут, если вы не сделаете этого (они просто будут напечатаны в журнале): [task setStandardError: pipe];
Комментарий Дэйва хороший. Однако вы можете использовать availableData вместо readDataToEndOfFile, потому что availableData будет блокироваться при отсутствии данных. Важно, если выполняемая команда потребляет много ресурсов ЦП и дает мало результатов, например gcc.
Можно ли использовать это в режиме песочницы? Потому что у меня это сработало. Но когда я включил режим песочницы, на выходе я получаю пустую строку. Кто-нибудь может мне помочь?
Это можно обновить с помощью ARC и литералов массива Obj-C. Например. pastebin.com/sRvs3CqD
Также рекомендуется передавать ошибки по конвейеру. task.standardError = pipe;
http 404 на сайте cocoadev.com ссылка :(
Вот кешированная версия этой ссылки: web.archive.org/web/20141121094204/https://cocoadev.com/…
Или, поскольку Objective C - это просто C с некоторым уровнем объектно-ориентированного программирования наверху, вы можете использовать компоненты 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.
вилка, exec и ждать должны работать, если вы действительно не ищете способ, специфичный для 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-совместимой системе.
Я знаю, что это очень старый ответ, но мне нужно сказать следующее: это отличный способ использовать trheads для обработки исключения. единственный недостаток в том, что он создает копию всей программы. поэтому для приложения какао я бы выбрал @GordonWilson для лучшего подхода, и если я работаю над приложением командной строки, это лучший способ сделать это. спасибо (извините, мой плохой английский)
Есть еще старый добрый POSIX система ("echo -en '\ 007'");
НЕ ВЫПОЛНЯЙТЕ ЭТУ КОМАНДУ. (Если вы не знаете, что делает эта команда)
Поменял на что-то более безопасное… (пищит)
Разве это не вызовет ошибку в консоли? Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Хм. Возможно, вам нужно дважды избежать обратной косой черты.
просто запустите / usr / bin / echo или что-то в этом роде. rm -rf жестко, а юникод в консоли все равно отстой :)
в духе совместного использования ... это метод, который я часто использую для запуска сценариев оболочки. вы можете добавить скрипт в пакет вашего продукта (на этапе копирования сборки), а затем пусть сценарий будет прочитан и запущен во время выполнения. примечание: этот код ищет сценарий в подпутье 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
Ссылка на объяснение мертва.
Я хочу запустить эту команду «system_profiler SPApplicationsDataType -xml», но получаю сообщение об ошибке «путь запуска недоступен»
Кустос Мортем сказал:
I'm surprised no one really got into blocking/non-blocking call issues
Информацию о проблемах с блокировкой / неблокирующими вызовами относительно NSTask читайте ниже:
asynctask.m -- sample code that shows how to implement asynchronous stdin, stdout & stderr streams for processing data with NSTask
Исходный код asynctask.m доступен на GitHub.
См. Мой вклад для неблокирующей версии
Если для команды терминала требуются права администратора (также известный как 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);
Это официально не рекомендуется с OS X 10.7.
... но он продолжает работать, несмотря на это, поскольку это единственный способ сделать это, и я считаю, что многие установщики полагаются на это.
Статья Кента дала мне новую идею. этому методу 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");
Это хорошо обрабатывает большинство случаев, но если вы запустите его в цикле, это в конечном итоге вызовет исключение из-за слишком большого количества дескрипторов открытых файлов. Можно исправить, добавив: [file closeFile]; после readDataToEndOfFile.
@DavidStein: Я думаю, что использование autoreleasepool для обертывания метода runCommand кажется более подходящим, чем. На самом деле, приведенный выше код также не рассматривает не-ARC.
@Kenial: О, это гораздо лучшее решение. Он также высвобождает ресурсы сразу после выхода из области видимости.
/ bin / ps: Операция запрещена, у меня ничего не получается, свинец?
Я написал эту функцию "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 для меня все еще вызывает недоумение ... и с небольшой верой в мою способность исправить мои грубые недостатки, описанные выше, - единственная оливковая ветвь, которую я предлагаю, - это режуженная версия ответа @ 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]; });
}
outP не определен при любой ошибке, chars_read слишком мал для возвращаемого значения fread () в любой архитектуре, где sizeof (ssize_t)! = sizeof (int), что, если нам нужно больше вывода, чем байтов BUFSIZ? Что делать, если вывод не UTF-8? Что делать, если pclose () возвращает ошибку? Как сообщить об ошибке fread ()?
@ ObjectiveC-oder D'oh - я не знаю. Пожалуйста, скажите мне (как в .. отредактировать)!
Очистили код в верхнем ответе, чтобы сделать его более читабельным, менее избыточным, добавили преимущества однострочный метод и превратили его в категорию 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];
Надеюсь, это будет так же полезно для вас, как и для меня в будущем. (Привет!)
Вот пример Swift с использованием 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()
Действительно, ваш код мне очень пригодился! Я изменил его на Swift и опубликовал как еще один ответ ниже.
Changes for Swift 3.0:
NSPipehas been renamedPipe
NSTaskhas been renamedProcess
Это основано на ответе Inkit Objective-C выше. Он написал это как категория на 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?, но это было бы неудобно использовать и бесполезно, потому что это маловероятно.
Действительно красивый и элегантный образ! Этот ответ должен иметь больше голосов.
Если вам не нужен вывод. Добавьте аргумент @discardableResult перед методом runCommand или над ним. Это позволит вам вызывать метод, не помещая его в переменную.
пусть результат = String (байты: fileHandle.readDataToEndOfFile (), кодировка: String.Encoding.utf8) в порядке
В дополнение к нескольким отличным ответам, приведенным выше, я использую следующий код для обработки вывода команды в фоновом режиме и избегания механизма блокировки [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];
}
Для меня решающее значение имела строка [self performSelectorInBackground: @selector (collectTaskOutput :) withObject: file];
Я просто заявляю очевидное: с песочницей вы не можете просто запускать приложения, которых нет в вашей песочнице, И они должны быть подписаны вами, чтобы разрешить это