从Cocoa应用程序执行一个terminal命令

我怎样才能从我的Objective-C Cocoa应用程序执行terminal命令(如grep )?

你可以使用NSTask 。 这是一个运行' /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); 

NSPipeNSFileHandle用于redirect任务的标准输出。

有关在Objective-C应用程序中与操作系统进行交互的更多详细信息,可以在Apple's Development Center上查看此文档: 与操作系统进行交互 。

编辑:包括解决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

本着分享的精神…这是我经常用来运行shell脚本的方法。 您可以将脚本添加到产品包(在构build的复制阶段),然后在运行时读取并运行脚本。 注意:此代码在privateFrameworks子path中查找脚本。 警告:这可能是部署产品的安全风险,但对于我们的内部开发,它是一种简单的方法来定制简单的东西(比如哪个主机rsync到…),而不需要重新编译应用程序,但只需编辑shell中的脚本。

 //------------------------------------------------------ -(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

肯特的文章给了我一个新的想法。 这个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"); 

这是如何在Swift中完成的

Swift 3.0的变化:

  • NSPipe已更名为Pipe

  • NSTask已更名为Process


这是基于上面的inkit的Objective-C的答案。 他把它写成了一个NSString类别 – 对于Swift,它成为了String扩展

扩展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 ---" } } } 

用法:

 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() } 

请注意,从Pipe读取的Process结果是一个NSString对象。 它可能是一个错误string,它也可以是一个空string,但它应该始终是一个NSString

所以,只要不是零,结果可以投射为一个Swift String并返回。

如果由于某种原因,根本不能从文件数据初始化NSString ,则函数返回一个错误消息。 该函数可能已被写入返回一个可选的String? ,但是使用起来会很尴尬,并且不会起到有用的作用,因为这种情况不太可能发生。

fork , exec和wait应该可以工作,如果你不是真的在寻找Objective-C特定的方法。 fork创build当前正在运行的程序的副本, exec用新的程序代替当前正在运行的程序, wait等待exec退出。 例如(没有任何错误检查):

 #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. */ 

还有一个系统 ,它运行命令就好像你从shell的命令行input一样。 这很简单,但是对这种情况的掌控很less。

我假设你正在使用Mac应用程序,所以链接是苹果公司的这些function的文档,但他们都是POSIX ,所以你应该在任何符合POSIX的系统上使用它们。

Objective-C(见下面的Swift)

清除顶部答案中的代码,使其更具可读性,减less冗余,增加了单行方法的优点,并将其作为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]; 

希望对未来的我来说,这对你是有用的。 (你好!)


斯威夫特4

这是一个使用PipeProcessString的Swift示例

 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() 

还有一个很好的旧POSIX 系统 (“echo -en'\ 007'”);

我写了这个“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]; }); } 

Custos Mortem说:

我很惊讶没有人真的进入阻塞/非阻塞的电话问题

有关NSTask阻塞/非阻塞呼叫问题, NSTask阅读以下内容:

asynctask.m – 示例代码,展示了如何实现用NSTask处理数据的asynchronousstdin,stdout和stderrstream

asynctask.m的源代码可以在GitHub上find 。

如果terminal命令需要pipe理员权限(又名sudo ),请改用AuthorizationExecuteWithPrivileges 。 下面将创build一个名为“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); 

或者因为Objective C只是C上面有一些OO层,你可以使用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[]); 

它们包含在unistd.h头文件中。