Làm cách nào tôi có thể thực thi lệnh đầu cuối (như grep
) từ ứng dụng Objective-C Ca cao của mình?
/usr/bin
nơi grep
sống.
Làm cách nào tôi có thể thực thi lệnh đầu cuối (như grep
) từ ứng dụng Objective-C Ca cao của mình?
/usr/bin
nơi grep
sống.
Câu trả lời:
Bạn có thể sử dụng NSTask
. Đây là một ví dụ sẽ chạy ' /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
và NSFileHandle
được sử dụng để chuyển hướng đầu ra tiêu chuẩn của nhiệm vụ.
Để biết thêm thông tin chi tiết về việc tương tác với hệ điều hành từ trong ứng dụng Objective-C của bạn, bạn có thể xem tài liệu này trên Trung tâm phát triển của Apple: Tương tác với Hệ điều hành .
Chỉnh sửa: Bao gồm sửa lỗi cho vấn đề NSLog
Nếu bạn đang sử dụng NSTask để chạy tiện ích dòng lệnh thông qua bash, thì bạn cần bao gồm dòng ma thuật này để giữ cho NSLog hoạt động:
//The magic line that keeps your log where it belongs
task.standardOutput = pipe;
Một lời giải thích có tại đây: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
NSMutableData *data = [NSMutableData dataWithCapacity:512];
. Sau đó , while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
. Và tôi "tin" bạn nên có thêm một lần nữa [data appendData:[file readDataToEndOfFile]];
sau khi thoát khỏi vòng lặp while.
task.standardError = pipe;
Bài viết của kent đã cho tôi một ý tưởng mới. phương thức runCommand này không cần tệp script, chỉ chạy một dòng lệnh:
- (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;
}
Bạn có thể sử dụng phương pháp này như thế này:
NSString *output = runCommand(@"ps -A | grep mysql");
trên tinh thần chia sẻ ... đây là phương pháp tôi thường xuyên sử dụng để chạy shell script. bạn có thể thêm tập lệnh vào gói sản phẩm của mình (trong giai đoạn sao chép của bản dựng) và sau đó để tập lệnh được đọc và chạy khi chạy. lưu ý: mã này tìm tập lệnh trong đường dẫn con privateFrameworks. cảnh báo: đây có thể là rủi ro bảo mật cho các sản phẩm được triển khai, nhưng đối với sự phát triển nội bộ của chúng tôi, đây là một cách dễ dàng để tùy chỉnh những thứ đơn giản (như máy chủ nào để rsync thành ...) mà không cần biên dịch lại ứng dụng, mà chỉ cần chỉnh sửa kịch bản shell trong gói.
//------------------------------------------------------
-(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);
}
//------------------------------------------------------
Chỉnh sửa: Bao gồm sửa lỗi cho vấn đề NSLog
Nếu bạn đang sử dụng NSTask để chạy tiện ích dòng lệnh thông qua bash, thì bạn cần bao gồm dòng ma thuật này để giữ cho NSLog hoạt động:
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Trong ngữ cảnh:
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Một lời giải thích có ở đây: http://www.cocoadev.com/index.pl?NSTask
Thay đổi cho Swift 3.0:
NSPipe
đã được đổi tênPipe
NSTask
đã được đổi tênProcess
Điều này dựa trên câu trả lời Objective-C của inkit ở trên. Anh ấy đã viết nó như một thể loại trên NSString
- Đối với Swift, nó trở thành một phần mở rộng của 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"
hoặc chỉ:
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()
}
Lưu ý Process
kết quả khi đọc từ Pipe
là một NSString
đối tượng. Nó có thể là một chuỗi lỗi và nó cũng có thể là một chuỗi rỗng, nhưng nó phải luôn là một chuỗi NSString
.
Vì vậy, miễn là nó không phải là con số không, kết quả có thể được chuyển thành Swift String
và được trả về.
Nếu vì một lý do nào đó, không có gì NSString
có thể được khởi tạo từ dữ liệu tệp, hàm sẽ trả về một thông báo lỗi. Hàm này có thể đã được viết để trả về một tùy chọn String?
, nhưng điều đó sẽ gây khó xử khi sử dụng và sẽ không phục vụ mục đích hữu ích vì điều này rất khó xảy ra.
Làm sạch mã trong câu trả lời hàng đầu để làm cho nó dễ đọc hơn, ít dư thừa hơn, thêm các lợi ích của phương pháp một dòng và được tạo thành một thể loại NSString
@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end
Thực hiện:
@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
Sử dụng:
NSString* output = [@"echo hello" runAsCommand];
Và nếu bạn gặp vấn đề với mã hóa đầu ra:
// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
Hy vọng nó hữu ích cho bạn vì nó sẽ tương lai với tôi. (Chào bạn!)
Dưới đây là một ví dụ Swift làm sử dụng Pipe
, Process
và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)
}
}
Sử dụng:
let output = "echo hello".run()
ngã ba , exec , và chờ đợi nên làm việc, nếu bạn không thực sự tìm kiếm một cụ thể cách Objective-C. fork
tạo một bản sao của chương trình hiện đang chạy, exec
thay thế chương trình hiện đang chạy bằng chương trình mới và wait
chờ quá trình con thoát ra. Ví dụ: (không có bất kỳ kiểm tra lỗi nào):
#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. */
Ngoài ra còn có hệ thống , chạy lệnh như thể bạn gõ nó từ dòng lệnh của shell. Nó đơn giản hơn, nhưng bạn có ít quyền kiểm soát tình huống hơn.
Tôi giả sử bạn đang làm việc trên một ứng dụng Mac, vì vậy các liên kết đến tài liệu của Apple cho các chức năng này, nhưng chúng là tất cả POSIX
, vì vậy bạn nên sử dụng chúng trên bất kỳ hệ thống nào tuân thủ POSIX.
Ngoài ra còn có hệ thống POSIX cũ tốt ("echo -en '\ 007'");
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Tôi đã viết hàm "C" này, vì NSTask
nó đáng ghét ..
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 ..
…
ồ, và vì lợi ích của việc hoàn thành / không mơ hồ
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Nhiều năm sau, C
vẫn là một mớ hỗn độn hoang mang, với tôi .. và với một chút niềm tin vào khả năng của mình để sửa chữa khuyết điểm tổng của tôi ở trên - Chi nhánh Tôi đề nghị chỉ ô liu là một phiên bản rezhuzhed của câu trả lời @ inket rằng là barest xương , cho đồng bào của tôi những người theo chủ nghĩa thuần túy / dài dòng ...
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 nói:
Tôi ngạc nhiên không ai thực sự gặp phải vấn đề chặn / không chặn cuộc gọi
Để chặn / không chặn các sự cố cuộc gọi liên quan đến NSTask
đọc bên dưới:
asynctask.m - mã mẫu cho biết cách triển khai các luồng stdin, stdout & stderr không đồng bộ để xử lý dữ liệu với NSTask
Mã nguồn của asynctask.m có sẵn tại GitHub .
Ngoài một số câu trả lời xuất sắc ở trên, tôi sử dụng đoạn mã sau để xử lý đầu ra của lệnh trong nền và tránh cơ chế chặn của [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];
}
Hoặc vì Objective C chỉ là C với một số lớp OO ở trên, bạn có thể sử dụng các conterparts posix:
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[]);
Chúng được bao gồm từ tệp tiêu đề unistd.h.
Nếu lệnh Terminal yêu cầu Đặc quyền quản trị viên (aka sudo
), AuthorizationExecuteWithPrivileges
thay vào đó hãy sử dụng . Sau đây sẽ tạo một tệp có tên "com.stackoverflow.test" là thư mục gốc "/ 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);