如何从Objective-C Cocoa应用程序执行终端命令(如grep) ?
当前回答
肯特的文章给了我一个新想法。这个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");
其他回答
验尸官说:
我很惊讶没有人真正陷入阻塞/非阻塞调用问题
关于NSTask的阻塞/非阻塞调用问题,请阅读以下内容:
asynctask。m——示例代码,展示了如何实现异步stdin, stdout和stderr流处理数据与NSTask
asynctask的源代码。m可以在GitHub上找到。
你可以使用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);
nspope和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
下面是如何在Swift中做到这一点
Swift 3.0的变化: nspape已被重命名为Pipe NSTask已重命名为Process
这是基于inkit的Objective-C答案上面。他把它写成NSString -的category 对于Swift来说,它成为了String的扩展。
extension String. runascommand() ->字符串
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对象。它可能是一个错误字符串,也可能是一个空字符串,但它应该总是一个NSString。
因此,只要它不是nil,结果就可以转换为Swift String并返回。
如果由于某种原因,没有NSString可以从文件数据初始化,该函数返回一个错误消息。函数可以被写成返回一个可选的String?,但这将是尴尬的使用,也不会起到有用的作用,因为这种情况不太可能发生。
fork, exec和wait应该有用,如果你不是在寻找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. */
还有一个系统,它运行命令,就像您从shell的命令行输入命令一样。这样更简单,但你对情况的控制力更弱。
我假设你正在使用一个Mac应用程序,所以这些链接指向苹果关于这些函数的文档,但它们都是POSIX,所以你应该在任何POSIX兼容的系统上使用它们。
除了上面的几个出色的答案之外,我还使用以下代码在后台处理命令的输出,并避免[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];
}
推荐文章
- 访问限制:'Application'类型不是API(必需库rt.jar的限制)
- 为什么在Mac OS X v10.9 (Mavericks)的终端中apt-get功能不起作用?
- “你有邮件”的消息在终端,os X
- 为什么ARC仍然需要@autoreleasepool ?
- Mac OS X中的环境变量
- 首先添加一个UIView,甚至是导航栏
- 我如何改变UIButton标题颜色?
- 如何从UIImage (Cocoa Touch)或CGImage (Core Graphics)获取像素数据?
- 如何从macOS完全卸载蟒蛇
- iOS -构建失败,CocoaPods无法找到头文件
- Xcode 4挂在“附加到(应用程序名称)”
- 为什么单元测试中的代码不能找到包资源?
- 如何配置Mac OS X术语,使git有颜色?
- 以编程方式创建segue
- 在Objective-C中@synchronized如何锁定/解锁?