Como posso executar um comando de terminal (como grep
) do meu aplicativo Objective-C Cocoa?
/usr/bin
onde grep
mora.
Como posso executar um comando de terminal (como grep
) do meu aplicativo Objective-C Cocoa?
/usr/bin
onde grep
mora.
Respostas:
Você pode usar NSTask
. Aqui está um exemplo que seria executado ' /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
e NSFileHandle
são usados para redirecionar a saída padrão da tarefa.
Para obter informações mais detalhadas sobre como interagir com o sistema operacional no aplicativo Objective-C, consulte este documento no Centro de Desenvolvimento da Apple: Interagindo com o sistema operacional .
Editar: Correção incluída para o problema NSLog
Se você estiver usando o NSTask para executar um utilitário de linha de comando via bash, precisará incluir esta linha mágica para manter o NSLog funcionando:
//The magic line that keeps your log where it belongs
task.standardOutput = pipe;
Uma explicação está aqui: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
NSMutableData *data = [NSMutableData dataWithCapacity:512];
. Então while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
,. E eu "acredito" que você deve ter mais um [data appendData:[file readDataToEndOfFile]];
após o ciclo while terminar.
task.standardError = pipe;
O artigo de Kent me deu uma nova idéia. esse método runCommand não precisa de um arquivo de script, apenas executa um comando por uma linha:
- (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;
}
Você pode usar este método como este:
NSString *output = runCommand(@"ps -A | grep mysql");
no espírito de compartilhar ... esse é um método que eu uso frequentemente para executar scripts de shell. você pode adicionar um script ao seu pacote de produtos (na fase de cópia da construção) e, em seguida, fazer com que o script seja lido e executado em tempo de execução. nota: esse código procura o script no subcaminho privateFrameworks. aviso: isso pode ser um risco à segurança dos produtos implantados, mas, para o nosso desenvolvimento interno, é uma maneira fácil de personalizar coisas simples (como qual host do rsync ...) sem recompilar o aplicativo, mas apenas editar o script de shell no pacote.
//------------------------------------------------------
-(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);
}
//------------------------------------------------------
Editar: Correção incluída para o problema NSLog
Se você estiver usando o NSTask para executar um utilitário de linha de comando via bash, precisará incluir esta linha mágica para manter o NSLog funcionando:
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
No contexto:
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Uma explicação está aqui: http://www.cocoadev.com/index.pl?NSTask
Alterações no Swift 3.0:
NSPipe
foi renomeadoPipe
NSTask
foi renomeadoProcess
Isto é baseado na resposta Objective-C do inkit acima. Ele escreveu como uma categoria em NSString
- Para Swift, isso se torna uma extensão de 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"
ou apenas:
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()
}
Observe o Process
resultado como lido a partir de Pipe
um NSString
objeto. Pode ser uma sequência de erros e também pode ser uma sequência vazia, mas sempre deve ser uma NSString
.
Portanto, desde que não seja nulo, o resultado pode ser convertido em Swift String
e retornado.
Se, por algum motivo, não for NSString
possível inicializar a partir dos dados do arquivo, a função retornará uma mensagem de erro. A função poderia ter sido escrita para retornar um opcional String?
, mas seria difícil de usar e não serviria a um propósito útil, porque é muito improvável que isso ocorra.
Limpou o código na resposta superior para torná-lo mais legível, menos redundante, adicionou os benefícios do método de uma linha e transformado em uma categoria NSString
@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end
Implementação:
@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
Uso:
NSString* output = [@"echo hello" runAsCommand];
E se você estiver tendo problemas com a codificação de saída:
// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
Espero que seja tão útil para você como será para o meu futuro. (Olá, você!)
Aqui está um exemplo Swift fazendo uso de Pipe
, Process
eString
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)
}
}
Uso:
let output = "echo hello".run()
fork , exec e wait devem funcionar, se você realmente não está procurando uma maneira específica de Objective-C. fork
cria uma cópia do programa atualmente em execução, exec
substitui o programa atualmente em execução por um novo e wait
aguarda a saída do subprocesso. Por exemplo (sem nenhuma verificação de erro):
#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. */
Há também o sistema , que executa o comando como se você o digitasse na linha de comando do shell. É mais simples, mas você tem menos controle sobre a situação.
Suponho que você esteja trabalhando em um aplicativo Mac, então os links são para a documentação da Apple para essas funções, mas são todas POSIX
, portanto, você deve usá-los em qualquer sistema compatível com POSIX.
Também existe um bom sistema POSIX antigo ("echo -en '\ 007'");
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Eu escrevi essa função "C", porque NSTask
é desagradável ..
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 ..
…
ah, e por uma questão de ser completa / inequívoca…
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Anos mais tarde, C
ainda é uma confusão desconcertante, para mim .. e com pouca fé na minha capacidade de corrigir meus defeitos brutas acima - a oferta só ramo de oliveira que é uma versão rezhuzhed de resposta de @ inket que é mais desencapado dos ossos , para o meu companheiro puristas / odiadores da verbosidade ...
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 disse:
Estou surpreso que ninguém realmente tenha entrado em problemas de chamadas de bloqueio / não bloqueio
Para problemas de chamada de bloqueio / não bloqueio relacionados à NSTask
leitura abaixo:
asynctask.m - código de exemplo que mostra como implementar fluxos assíncronos stdin, stdout e stderr para processar dados com o NSTask
O código fonte do asynctask.m está disponível no GitHub .
Além das várias excelentes respostas acima, eu uso o código a seguir para processar a saída do comando em segundo plano e evitar o mecanismo de bloqueio de [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];
}
Ou como o Objective C é apenas C com uma camada OO na parte superior, você pode usar as contrapartes 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[]);
Eles estão incluídos no arquivo de cabeçalho unistd.h.
Se o comando Terminal exigir privilégio de administrador (aka sudo
), use em seu AuthorizationExecuteWithPrivileges
lugar. A seguir, será criado um arquivo chamado "com.stackoverflow.test", o diretório raiz "/ 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);