Bagaimana saya bisa menjalankan perintah terminal (seperti grep
) dari aplikasi Kakao Objective-C saya?
/usr/bin
tempat grep
tinggal.
Bagaimana saya bisa menjalankan perintah terminal (seperti grep
) dari aplikasi Kakao Objective-C saya?
/usr/bin
tempat grep
tinggal.
Jawaban:
Anda bisa menggunakannya NSTask
. Berikut ini contoh yang akan dijalankan ' /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
dan NSFileHandle
digunakan untuk mengarahkan output standar dari tugas tersebut.
Untuk informasi lebih rinci tentang interaksi dengan sistem operasi dari dalam aplikasi Objective-C Anda, Anda dapat melihat dokumen ini di Pusat Pengembangan Apple: Berinteraksi dengan Sistem Operasi .
Sunting: Termasuk perbaikan untuk masalah NSLog
Jika Anda menggunakan NSTask untuk menjalankan utilitas baris perintah melalui bash, maka Anda perlu menyertakan jalur ajaib ini agar NSLog berfungsi:
//The magic line that keeps your log where it belongs
task.standardOutput = pipe;
Penjelasannya ada di sini: https://web.archive.org/web/20141121094204/https://cocoadev.com/HowToPipeCommandsWithNSTask
NSMutableData *data = [NSMutableData dataWithCapacity:512];
. Lalu while ([task isRunning]) { [data appendData:[file readDataToEndOfFile]]; }
,. Dan saya "percaya" Anda harus memiliki satu lagi [data appendData:[file readDataToEndOfFile]];
setelah loop sementara keluar.
task.standardError = pipe;
Artikel kent memberi saya ide baru. Metode runCommand ini tidak memerlukan file skrip, cukup jalankan perintah dengan satu baris:
- (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;
}
Anda dapat menggunakan metode ini seperti ini:
NSString *output = runCommand(@"ps -A | grep mysql");
dalam semangat berbagi ... ini adalah metode yang sering saya gunakan untuk menjalankan skrip shell. Anda dapat menambahkan skrip ke bundel produk Anda (dalam fase salin build) dan kemudian meminta skrip dibaca dan dijalankan pada saat runtime. catatan: kode ini mencari skrip di sub-jalur privateFrameworks. peringatan: ini bisa menjadi risiko keamanan untuk produk yang digunakan, tetapi untuk pengembangan in-house kami, ini adalah cara mudah untuk menyesuaikan hal-hal sederhana (seperti host mana untuk rsync ke ...) tanpa mengkompilasi ulang aplikasi, tetapi hanya mengedit skrip shell dalam bundel.
//------------------------------------------------------
-(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);
}
//------------------------------------------------------
Sunting: Termasuk perbaikan untuk masalah NSLog
Jika Anda menggunakan NSTask untuk menjalankan utilitas baris perintah melalui bash, maka Anda perlu menyertakan jalur ajaib ini agar NSLog berfungsi:
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Dalam konteks:
NSPipe *pipe;
pipe = [NSPipe pipe];
[task setStandardOutput: pipe];
//The magic line that keeps your log where it belongs
[task setStandardInput:[NSPipe pipe]];
Penjelasannya ada di sini: http://www.cocoadev.com/index.pl?NSTask
Perubahan untuk Swift 3.0:
NSPipe
telah diganti namanyaPipe
NSTask
telah diganti namanyaProcess
Ini didasarkan pada jawaban Objectit-C di atas. Dia menulisnya sebagai kategori di NSString
- Untuk Swift, itu menjadi perpanjangan dari 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"
atau hanya:
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()
}
Perhatikan Process
hasil sebagai membaca dari Pipe
sebuah NSString
objek. Mungkin string kesalahan dan juga bisa berupa string kosong, tetapi harus selalu berupa NSString
.
Jadi, selama tidak nihil, hasilnya bisa berubah menjadi Swift String
dan dikembalikan.
Jika karena alasan tertentu tidak ada NSString
sama sekali dapat diinisialisasi dari data file, fungsi mengembalikan pesan kesalahan. Fungsi itu bisa saja ditulis untuk mengembalikan opsional String?
, tetapi itu akan menjadi canggung untuk digunakan dan tidak akan melayani tujuan yang bermanfaat karena sangat tidak mungkin hal ini terjadi.
Membersihkan kode di jawaban atas untuk membuatnya lebih mudah dibaca, kurang berlebihan, menambahkan manfaat dari metode satu baris dan dibuat menjadi kategori NSString
@interface NSString (ShellExecution)
- (NSString*)runAsCommand;
@end
Penerapan:
@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
Pemakaian:
NSString* output = [@"echo hello" runAsCommand];
Dan jika Anda mengalami masalah dengan pengkodean keluaran:
// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
Semoga bermanfaat bagi Anda dan masa depan saya. (Hai kamu!)
Berikut adalah Swift contoh pembuatan penggunaan Pipe
, Process
danString
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)
}
}
Pemakaian:
let output = "echo hello".run()
fork , exec , dan wait harus berfungsi, jika Anda tidak benar-benar mencari cara spesifik Objective-C. fork
membuat salinan dari program yang sedang berjalan, exec
menggantikan program yang sedang berjalan dengan yang baru, dan wait
menunggu subproses untuk keluar. Misalnya (tanpa memeriksa kesalahan):
#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. */
Ada juga sistem , yang menjalankan perintah seolah-olah Anda mengetiknya dari baris perintah shell. Ini lebih sederhana, tetapi Anda memiliki sedikit kendali atas situasi.
Saya berasumsi Anda sedang mengerjakan aplikasi Mac, jadi tautannya adalah ke dokumentasi Apple untuk fungsi-fungsi ini, tetapi semuanya POSIX
, jadi Anda harus menggunakannya pada sistem yang mendukung POSIX.
Ada juga sistem POSIX lama yang bagus ("echo -en '\ 007'");
Incorrect NSStringEncoding value 0x0000 detected. Assuming NSStringEncodingASCII. Will stop this compatibility mapping behavior in the near future.
Saya menulis fungsi "C" ini, karena NSTask
menjengkelkan ..
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 ..
…
oh, dan demi menjadi lengkap / tidak ambigu ...
#define $UTF8(A) ((NSString*)[NSS stringWithUTF8String:A])
Bertahun-tahun kemudian, C
masih menjadi kekacauan yang membingungkan, bagi saya .. dan dengan sedikit kepercayaan pada kemampuan saya untuk memperbaiki kekurangan kotor saya di atas - satu-satunya cabang zaitun yang saya tawarkan adalah versi rezhuzhed dari jawaban @ inket yang paling sederhana tulangnya , untuk rekan saya purists / pembenci verbositas ...
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 berkata:
Saya terkejut tidak ada yang benar-benar masuk ke masalah panggilan pemblokiran / non-pemblokiran
Untuk masalah pemblokiran / non-pemblokiran terkait NSTask
membaca di bawah ini:
asynctask.m - kode sampel yang menunjukkan cara menerapkan asynchronous stdin, stdout & stderr stream untuk memproses data dengan NSTask
Kode sumber asynctask.m tersedia di GitHub .
Selain beberapa jawaban yang sangat baik di atas, saya menggunakan kode berikut untuk memproses output dari perintah di latar belakang dan menghindari mekanisme pemblokiran [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];
}
Atau karena Objective C hanya C dengan beberapa layer OO di atas Anda dapat menggunakan 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[]);
Mereka disertakan dari file header unistd.h.
Jika perintah Terminal membutuhkan Administrator Privilege (alias sudo
), gunakan AuthorizationExecuteWithPrivileges
saja. Berikut ini akan membuat file bernama "com.stackoverflow.test" adalah direktori root "/ 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);