ฉันจะรันคำสั่งเทอร์มินัล (เหมือนgrep
) จากแอปพลิเคชัน Objective-C Cocoa ของฉันได้อย่างไร
/usr/bin
ที่ที่มีgrep
ชีวิต
ฉันจะรันคำสั่งเทอร์มินัล (เหมือนgrep
) จากแอปพลิเคชัน Objective-C Cocoa ของฉันได้อย่างไร
/usr/bin
ที่ที่มีgrep
ชีวิต
คำตอบ:
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-loop
task.standardError = pipe;
บทความของ kent ทำให้ฉันมีความคิดใหม่ เมธอด 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");
ในจิตวิญญาณของการแบ่งปัน ... นี่เป็นวิธีที่ฉันใช้เป็นประจำเพื่อเรียกใช้เชลล์สคริปต์ คุณสามารถเพิ่มสคริปต์ลงในชุดผลิตภัณฑ์ของคุณ (ในขั้นตอนการคัดลอกของบิลด์) จากนั้นให้สคริปต์อ่านและรันในรันไทม์ หมายเหตุ: รหัสนี้จะค้นหาสคริปต์ในเส้นทางย่อย privateFrameworks คำเตือน: นี่อาจเป็นความเสี่ยงด้านความปลอดภัยสำหรับผลิตภัณฑ์ที่ปรับใช้ แต่สำหรับการพัฒนาภายใน บริษัท ของเรามันเป็นวิธีที่ง่ายในการปรับแต่งสิ่งต่าง ๆ (เช่นโฮสต์ที่ rsync ไปที่ ... ) โดยไม่ต้องรวบรวมแอปพลิเคชันใหม่ เชลล์สคริปต์ในบันเดิล
//------------------------------------------------------
-(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
การเปลี่ยนแปลงสำหรับ Swift 3.0:
NSPipe
ถูกเปลี่ยนชื่อแล้วPipe
NSTask
ถูกเปลี่ยนชื่อแล้วProcess
นี่คือคำตอบ Objective-C ของ inkit ข้างต้น เขาเขียนว่ามันเป็นหมวดหมู่บน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
แต่มันก็ควรจะเป็น
ดังนั้นตราบใดที่มันไม่ใช่ศูนย์ผลที่ได้ก็จะกลายเป็นสวิฟท์ String
และกลับมา
หากด้วยเหตุผลบางอย่างที่ไม่NSString
สามารถเริ่มต้นได้จากข้อมูลไฟล์ฟังก์ชั่นจะส่งกลับข้อผิดพลาด ฟังก์ชั่นนี้อาจถูกเขียนขึ้นเพื่อส่งกลับทางเลือก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()
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. */
นอกจากนี้ยังมีระบบซึ่งรันคำสั่งราวกับว่าคุณพิมพ์จากบรรทัดคำสั่งของเชลล์ ง่ายกว่า แต่คุณสามารถควบคุมสถานการณ์ได้น้อยลง
ฉันสมมติว่าคุณกำลังทำงานกับแอปพลิเคชั่น 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 รุ่น rezhuzhed ที่แทบไม่มีกระดูกสำหรับเพื่อนของฉัน คนเจ้าระเบียบ / การใช้คำฟุ่มเฟื่อย ...
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
อ่านด้านล่าง:
asynctask.m - โค้ดตัวอย่างที่แสดงวิธีใช้ stdin แบบอะซิงโครนัส, stdout & stderr สำหรับการประมวลผลข้อมูลด้วย NSTask
[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];
}
หรือเนื่องจาก 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
หากคำสั่ง Terminal ต้องการสิทธิ์ของผู้ดูแลระบบ (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);