#swiftで外部コマンド実行
頭に#!/usr/bin/swift
って書いておくと
chmod a+x hoge.swift
./hoge.swift #swift hoge.swiftでも起動できる
って感じでスクリプトみたいに起動できることを知ったので、勉強がてら手元にあるperlスクリプトをswift翻訳してた。
その際のメモ。
perlの
my $status = system "ls hoge/" #ret-code
my $ret = `ls hoge/ ` #output
こういうのをswiftでどう書けばいいんだっけ?と思って調べた。
##単純呼び出し
OSXアプリはほとんど書いたことないので、使ったことなかったけどNSTaskでできるみたい。
task.standardOutput
(NSPipe)で外部コマンドの出力を受け取ることができる。
カレントディレクトリを指定して実行したい場合は、task.currentDirectoryPath
にセットしておけばいい。
func stdOutOfCommand(cmd: String, arguments args: [String], currentDirPath currentDir: String? = nil) -> String {
let task: NSTask = NSTask()
task.launchPath = cmd
task.arguments = args
if currentDir != nil { task.currentDirectoryPath = currentDir! }
let pipe: NSPipe = NSPipe()
task.standardOutput = pipe
task.launch()
let out: NSData = pipe.fileHandleForReading.readDataToEndOfFile()
let outStr: String? = NSString(data: out, encoding: NSUTF8StringEncoding) as? String
return outStr == nil ? "" : outStr!
}
var ret = stdOutOfCommand("/bin/ls", arguments: ["hoge/"])
##対話形式
対話(入力を要求する)形式の場合は、waitForDataInBackgroundAndNotify()
でバックグラウンド待機と通知するようにして、
NSNotificationCenterでNSFileHandleDataAvailableNotification
を受け取るようにする必要がある。
taskの終了まで待ちたい場合はtask.waitUntilExit()
で待つ。
終了ステータスはtask.terminationStatus
で取得できる。
下の例だと、入力の制御はNSFileHandle.fileHandleWithStandardInput()
を使っている。
flushしてなかったり、addObserverのobjectをinPipeにしててはまった...orz
func printFlush(message: String) {
print(message, terminator: "")
fflush(__stdoutp)
}
func scriptWithCmd(cmd: String, arguments args: [String], currentDirPath currentDir: String? = nil) -> Int32 {
//set task
let input: NSFileHandle = NSFileHandle.fileHandleWithStandardInput()
let inPipe: NSPipe = NSPipe()
let outPipe: NSPipe = NSPipe()
let task: NSTask = NSTask()
task.launchPath = cmd
task.arguments = args
if currentDir != nil { task.currentDirectoryPath = currentDir! }
task.standardOutput = outPipe
task.standardInput = inPipe
task.launch()
//notification
input.waitForDataInBackgroundAndNotify()
outPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification, object: input, queue: nil,
usingBlock : { (notification: NSNotification!) in
let inData: NSData = input.availableData
if inData.length > 0 {
inPipe.fileHandleForWriting.writeData(inData)
input.waitForDataInBackgroundAndNotify()
} else {
inPipe.fileHandleForWriting.closeFile()
}
}
)
NSNotificationCenter.defaultCenter().addObserverForName(NSFileHandleDataAvailableNotification, object: outPipe.fileHandleForReading, queue: nil,
usingBlock: { (notification: NSNotification!) in
let outData: NSData = outPipe.fileHandleForReading.availableData
if let outStr: NSString = NSString(data: outData, encoding: NSUTF8StringEncoding) {
printFlush(outStr as String)
}
outPipe.fileHandleForReading.waitForDataInBackgroundAndNotify()
}
)
task.waitUntilExit()
return task.terminationStatus
}
##他の言語で外部コマンド呼び出し簡易メモ
以前少し調べてたやつ
####Perl
exec()
もあるけど戻ってこないのでsystem()
を使う。
バッククォート便利.
my $status = system "ls hoge/" #ret-code
my $ret = `ls hoge/ ` #output
####C
以前perlだと遅すぎるので、C/C++でやろうとして色々やっていた時のメモ
- system()
#include <stdlib.h>
int ret = system("ls hoge/");
- popen()/pclose()
POSIXだとpopen/pclose
とか使える.
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#define BUF 256
int main (void) {
FILE *fp;
char buf[BUF];
char *cmd = "/bin/ls hoge/";
if ((fp=popen(cmd,"r")) == NULL) {
err(EXIT_FAILURE, "%s", cmd);
}
while(fgets(buf, BUF, fp) != NULL) {
fputs(buf, stdout);
}
pclose(fp);
return 0;
}
- exec*()
popenだと入出力が同時にできないので、pipe()
, dup()
, fork()
, exec*()
あたりを使う必要がある。
####C++
PStreams(POSIX)が使えそう
####Java
ProcessBuilderを使うみたいだが、
シェルが直接解釈するコマンドは直接は実行できない?
参考
####JavaScript(Node.js)
execSync = require("child_process").execSync;
result = execSync("ls");
##参考