読者です 読者をやめる 読者になる 読者になる

kanetaiの二次記憶装置

プログラミングに関するやってみた、調べた系のものをQitaに移して、それ以外をはてブでやる運用にしようと思います。http://qiita.com/kanetai

swiftで外部コマンド実行

qiita.com に移動

頭に#!/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 {
    var 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 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
            let outStr: NSString = NSString(data: outData, encoding: NSUTF8StringEncoding)!
            print(outStr)
            fflush(__stdoutp)
            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を使うみたいだが、 シェルが直接解釈するコマンドは直接は実行できない?

参考

参考