LoginSignup
17
21

More than 5 years have passed since last update.

swiftで外部コマンド実行

Last updated at Posted at 2015-11-21

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");

参考

17
21
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
21