从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);
NSPipe
和NSFileHandle
用于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
这是一个使用Pipe
, Process
和String
的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头文件中。