swiftで外部コマンド実行

#swiftで外部コマンド実行
頭に`#!/usr/bin/swift`って書いておくと

```sh
chmod a+x hoge.swift
./hoge.swift #swift hoge.swiftでも起動できる
```

って感じでスクリプトみたいに起動できることを知ったので、勉強がてら手元にあるperlスクリプトをswift翻訳してた。
その際のメモ。

perlの

```perl
my $status = system "ls hoge/"  #ret-code
my $ret = `ls hoge/ `           #output
```

こういうのをswiftでどう書けばいいんだっけ？と思って調べた。

##単純呼び出し

OSXアプリはほとんど書いたことないので、使ったことなかったけど[NSTask](https://developer.apple.com/library/prerelease/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTask_Class/)でできるみたい。
`task.standardOutput` (NSPipe)で外部コマンドの出力を受け取ることができる。
カレントディレクトリを指定して実行したい場合は、`task.currentDirectoryPath`にセットしておけばいい。

```swift
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*

```swift
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()`を使う。
バッククォート便利.

```perl
my $status = system "ls hoge/"  #ret-code
my $ret = `ls hoge/ `           #output
```

####C

以前perlだと遅すぎるので、C/C++でやろうとして色々やっていた時のメモ

- system()

```c
#include <stdlib.h>
	int ret = system("ls hoge/");
```

- popen()/pclose()

POSIXだと`popen/pclose`とか使える.

```c
#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*()`あたりを使う必要がある。

[参考](http://blog.kenkov.jp/2013/04/12/pip_dup.html)

####C++

[PStreams](http://pstreams.sourceforge.net/)(POSIX)が使えそう

####Java

[ProcessBuilder](http://docs.oracle.com/javase/jp/7/api/java/lang/ProcessBuilder.html)を使うみたいだが、
シェルが直接解釈するコマンドは直接は実行できない？

参考

- [Java外部プロセス起動メモ(Hishidama's Java Process Memo)](http://www.ne.jp/asahi/hishidama/home/tech/java/process.html)
- [Java SE 7のProcessBuilderでリダイレクト](http://n-agetsuma.hatenablog.com/entry/2014/02/12/215321)

####JavaScript(Node.js)

```js
execSync = require("child_process").execSync;
result = execSync("ls");
```

##参考

- [NSTask](https://developer.apple.com/library/prerelease/mac/documentation/Cocoa/Reference/Foundation/Classes/NSTask_Class/)
- [How to use NSTask run terminal commands in loop in consistent environment?](http://stackoverflow.com/questions/13217415/how-to-use-nstask-run-terminal-commands-in-loop-in-consistent-environment)
- [pipe とdup とfork とっ](http://blog.kenkov.jp/2013/04/12/pip_dup.html)
- [PStreams](http://pstreams.sourceforge.net/)
- [Java外部プロセス起動メモ(Hishidama's Java Process Memo)](http://www.ne.jp/asahi/hishidama/home/tech/java/process.html)
- [Java SE 7のProcessBuilderでリダイレクト](http://n-agetsuma.hatenablog.com/entry/2014/02/12/215321)