Answers:
您可以使用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);
NSPipe
并且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
NSMutableData *data = [NSMutableData dataWithCapacity:512];
。然后,while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
。我“相信” [data appendData:[file readDataToEndOfFile]];
在while循环退出后,您应该再拥有一个。
task.standardError = pipe;
肯特的文章给了我一个新的主意。这个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");
本着共享的精神...这是我经常使用的运行shell脚本的方法。您可以在产品包中添加脚本(在构建的复制阶段),然后读取脚本并在运行时运行。注意:此代码在privateFrameworks子路径中查找脚本。警告:这对于已部署的产品可能存在安全风险,但是对于我们内部开发而言,这是一种自定义简单内容(例如,将主机同步到...的主机)而无需重新编译应用程序的简单方法,而只需编辑捆绑软件中的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]];
Swift 3.0的更改:
NSPipe
已重命名Pipe
NSTask
已重命名Process
这是基于上面inkit的Objective-C答案。他写了它作为一个类别上NSString
-对于斯威夫特,它成为一个扩展的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()
}
请注意,Process
从中读取的结果Pipe
是一个NSString
对象。它可能是错误字符串,也可能是空字符串,但应始终为NSString
。
因此,只要不为零,结果就可以转换为Swift String
并返回。
如果由于某种原因根本NSString
无法从文件数据中初始化任何内容,则该函数将返回错误消息。该函数可以编写为返回一个optional String?
,但是使用起来很尴尬,并且没有用处,因为它不太可能发生。
整理最佳答案中的代码,使其更具可读性,减少冗余,添加了单行方法的优点,并将其归为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];
希望它对您和对我未来都一样有用。(你好!)
这里有一个例子斯威夫特利用的Pipe
,Process
和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)
}
}
用法:
let output = "echo hello".run()
如果您并不是真的在寻找一种Objective-C特定的方式,那么fork,exec和wait应该可以工作。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. */
还有system,它可以像在shell命令行中键入命令一样运行命令。这比较简单,但是您对情况的控制较少。
我假设您正在使用Mac应用程序,因此这些功能的链接均指向Apple的文档,但它们都是POSIX
,因此您应该在任何POSIX兼容系统上使用它们。
还有一个很好的旧POSIX 系统(“ echo -en'\ 007'”);
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
我写了这个“ 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]; });
}
除了上述几个出色的答案外,我还使用以下代码在后台处理命令的输出,并避免了的阻塞机制[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];
}
或者,因为目标C只是在顶部具有OO层的C,所以可以使用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头文件中。
如果“终端”命令需要管理员特权(aka sudo
),请AuthorizationExecuteWithPrivileges
改用。下面将创建一个名为“ 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);