Objective-C Cocoaアプリケーションから端末コマンド(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
共有の精神で...これは、シェルスクリプトを実行するために頻繁に使用する方法です。 (ビルドのコピーフェーズで)製品バンドルにスクリプトを追加し、実行時にスクリプトを読み取って実行できます。注:このコードは、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]];
ケントの記事は私に新しいアイデアを与えました。このrunCommandメソッドはスクリプトファイルを必要とせず、1行でコマンドを実行するだけです。
- (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");
Swift 3.0の変更点:
NSPipe
の名前が変更されましたPipe
NSTask
の名前が変更されましたProcess
これは、上記のinkitのObjective-Cの回答に基づいています。彼はそれをcategory on NSString
として書きました— Swiftの場合、extension of 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
である必要があります。
そのため、nilでない限り、結果はSwift String
としてキャストして返すことができます。
何らかの理由でファイルデータからNSString
を初期化できない場合、関数はエラーメッセージを返します。この関数は、オプションのString?
を返すように書かれている可能性がありますが、これは使用するのが面倒で、これが発生する可能性は低いため、有用な目的には役立ちません。
トップアンサーのコードを整理して読みやすく、冗長性を減らし、 1行のメソッド の利点を追加し、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];
そして、if出力エンコーディングに問題があります:
// Had problems with `lsof` output and Japanese-named files, this fixed it
NSString* output = [@"export LANG=en_US.UTF-8;echo hello" runAsCommand];
それが将来の私にとってもそうであるように、あなたにとって有益であることを願っています。 (こんにちは、あなた!)
以下は、Swiftの例で、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. */
system もあり、シェルのコマンドラインから入力したかのようにコマンドを実行します。簡単ですが、状況を制御することはできません。
Macアプリケーションで作業していると想定しているため、リンクはこれらの機能に関するAppleのドキュメントへのリンクですが、これらはすべてPOSIX
なので、POSIX準拠のシステムで使用する必要があります。
古き良きPOSIXもあります system ( "echo -en '\ 007'");
NSTask
は不快であるため、この「C」関数を作成しました。
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
は今でも戸惑う混乱です。そして、上記の私の重大な欠点を修正する能力をほとんど信じていません。 骨の骨、仲間の純粋主義者/冗長性嫌い...
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]; });
}
クストス・モルテムは言った:
誰も実際にブロッキング/ノンブロッキングコールの問題に巻き込まれていないことに驚いています
NSTask
に関するブロッキング/ノンブロッキングコールの問題については、以下をお読みください。
asynctask.m-NSTaskでデータを処理するための非同期stdin、stdout、stderrストリームを実装する方法を示すサンプルコード
Asynctask.mのソースコードは GitHub で入手できます。
ターミナルコマンドに管理者権限(別名Sudo
)が必要な場合は、代わりにAuthorizationExecuteWithPrivileges
を使用します。以下は、ルートディレクトリ「/ System/Library/Caches」に「com.stackoverflow.test」という名前のファイルを作成します。
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);
上記のいくつかの優れた答えに加えて、次のコードを使用して、コマンド出力をバックグラウンドで処理し、[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は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ヘッダーファイルからインクルードされます。