Execute um comando de terminal a partir de um aplicativo Cocoa

Como posso executar um comando de terminal (como grep ) do meu aplicativo Objective-C Cocoa?

Você pode usar o NSTask . Aqui está um exemplo que /usr/bin/grep foo bar.txt/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 redirect a saída padrão da tarefa.

Para obter informações mais detalhadas sobre como interagir com o sistema operacional a partir do seu aplicativo Objective-C, você pode ver 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, será necessário include essa 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

no espírito de compartilhar … este é um método que eu uso freqüentemente para executar scripts de shell. você pode adicionar um script ao seu pacote de produtos (na fase de cópia da compilation) e depois fazer com que o script seja lido e executado no tempo de execução. observação: esse código procura o script no subcaminho privateFrameworks. aviso: isso poderia ser um risco de segurança para produtos implementados, mas para o nosso desenvolvimento interno é uma maneira fácil de personalizar coisas simples (como qual host para rsync para …) sem re-compilar o aplicativo, mas apenas editando 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, será necessário include essa 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

O artigo de kent me deu uma nova ideia. este 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 assim:

 NSString *output = runCommand(@"ps -A | grep mysql"); 

Veja como fazer isso no Swift

Mudanças para o Swift 3.0:

  • NSPipe foi renomeado para Pipe

  • NSTask foi renomeado como Process


Isto é baseado na resposta Objective-C do inkit acima. Ele escreveu como uma categoria em NSString – Para Swift, ela se torna uma extensão de String .

extensão String.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 ---" } } } 

Uso:

 let input = "echo hello" let output = input.runAsCommand() print(output) // prints "hello" 

ou apenas:

 print("echo hello".runAsCommand()) // prints "hello" 

Exemplo:

 @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 que o resultado do Process conforme lido no Pipe é um object NSString . Pode ser uma string de erro e também pode ser uma string vazia, mas deve sempre ser uma NSString .

Então, contanto que não seja nulo, o resultado pode ser convertido como uma String Swift e retornado.

Se, por algum motivo, nenhum NSString puder ser inicializado a partir dos dados do arquivo, a function retornará uma mensagem de erro. A function poderia ter sido escrita para retornar uma String? opcional String? , mas isso seria estranho de usar e não serviria a um propósito útil porque é muito improvável que isso ocorra.

fork , exec e wait devem funcionar, se você não estiver realmente procurando uma maneira específica do Objective-C. fork cria uma cópia do programa em execução no momento, exec substitui o programa em execução no momento por um novo e aguarda a espera para o subprocess sair. Por exemplo (sem nenhuma verificação de erro):

 #include  #include  

 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 tivesse typescript na linha de comando do shell. É mais simples, mas você tem menos controle sobre a situação.

Estou assumindo que você está trabalhando em um aplicativo Mac, então os links são para a documentação da Apple para essas funções, mas são todos POSIX , então você deve usá-los em qualquer sistema compatível com POSIX.

Objetivo-C (veja abaixo para Swift)

Limpou o código na resposta principal para torná-lo mais legível, menos redundante, adicionou os benefícios do método de uma linha e transformou-o em uma categoria de 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ê está 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 futuro de mim. (Oi você!)


Swift 4

Aqui está um exemplo do Swift fazendo uso de Pipe , Process e 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) } } 

Uso:

 let output = "echo hello".run() 

Também existe um bom sistema POSIX antigo (“echo -en ‘\ 007′”);

Eu escrevi essa function “C”, porque o 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 completo / inequívoco …

 #define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A]) 

Anos mais tarde, C ainda é uma bagunça desconcertante, para mim … e com pouca fé na minha capacidade de corrigir as minhas falhas – o único ramo de oliveira que ofereço é uma versão rezhuzhed da resposta de @ketket que é mais simples de ossos , para minha puristas companheiros / inimigos de 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 entrou em problemas de bloqueio / não-bloqueio de chamadas

Para questões de bloqueio / não bloqueio de chamadas em relação ao NSTask leia abaixo:

asynctask.m – código de amostra que mostra como implementar streams stdin, stdout & stderr asynchronouss para processar dados com NSTask

O código fonte do asynctask.m está disponível no GitHub .

Ou, como o Objective C é apenas C com alguma camada OO no topo, você pode usar o posix conterparts:

 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 header unistd.h.

Se o comando Terminal exigir Privilégio de Administrador (também conhecido como sudo ), use AuthorizationExecuteWithPrivileges . O seguinte irá criar 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);