LoginSignup
3
3

More than 5 years have passed since last update.

[Swift] Processを使おう!(4)

Posted at

Processってなに?

ちょっと前までNSTaskと呼ばれていた、主にコマンドラインツールを実行してデータのやり取りを行うためのクラスです。
SwiftでできることはSwiftでやってしまってもいいのですが、swiftcを呼びたいとかそういう時に使います。

の4回目。

第1回
第2回
第3回

振り返り

前回出力を得るカスタムオペレーションを追加して下のようになりました。

Process() <<< "/bin/echo" <<< ["Hello, World."] >>> { output in

    output.string.map { print($0) }
}

完璧です。

ところで皆さん。コマンドラインにおけるパイプラインってご存知ですか?
そうです。|です。

これを今までの成果を利用して書いてみましょう。

let echo = Process() <<< "/bin/echo" <<< ["Hello, World.\nこんにちは世界。"]
let grep = Process() <<< "/usr/bin/grep" <<< ["Hello"]

let pipe = Pipe()
echo.standardOutput = pipe
grep.standardInput = pipe

echo.launch()

grep >>> { output in
   output.string.map { print($0) }
}

またPipelaunch()が現れた!!!

パイプも簡単に

パイプを行うカスタムオペレーションを追加します。

func | (lhs: Process, rhs: Process) -> Process {

    let pipe = Pipe()

    lhs.standardOutput = pipe
    rhs.standardInput = pipe
    lhs.launch()

    return rhs
}

ふたつのProcessをパイプし新しいProcessを取り出します。
右側が返ってくるだけですが、中身が見えているからそう思うだけです!
ふたつのProcessがパイプされて新しいProcessが生まれたのです。

使用法

Process() <<< "/bin/echo" <<< ["Hello, World.\nこんにちは世界。"]
    | Process() <<< "/usr/bin/grep" <<< ["Hello"]
    >>> { output in

    output.string.map { print($0) }
}

短い! わかりやすい!

まとめ

今まで作ったカスタムな何かをまとめたものがこちらです。

/// 出力を簡単に扱うための補助的な型。FileHandleを隠蔽する。
struct Output {

    private let fileHandle: FileHandle

    init(fileHandle: FileHandle) {
        self.fileHandle = fileHandle
    }

    var data: Data {
        return fileHandle.readDataToEndOfFile()
    }

    var string: String? {
        return String(data: data, encoding: .utf8)
    }

    var lines: [String] {
        return string?.components(separatedBy: "\n") ?? []
    }
}

precedencegroup ArgumentPrecedence {

    associativity: left
    higherThan: AdditionPrecedence
}
infix operator <<< : ArgumentPrecedence

/// Processにexecutable pathを設定する。
func <<< (lhs: Process, rhs: String) -> Process {

    lhs.executableURL = URL(fileURLWithPath: rhs)
    return lhs
}

/// Processに引数を設定する。
func <<< (lhs: Process, rhs: [String]) -> Process {

    lhs.arguments = rhs
    return lhs
}

/// Processをパイプする。
func | (lhs: Process, rhs: Process) -> Process {

    let pipe = Pipe()

    lhs.standardOutput = pipe
    rhs.standardInput = pipe
    lhs.launch()

    return rhs
}

precedencegroup RedirectPrecedence {

    associativity: left
    lowerThan: LogicalDisjunctionPrecedence
}
infix operator >>> : RedirectPrecedence

/// Processの出力をOutput型で受け取り加工などができる。
/// ジェネリクスを利用しているのでどのような型にでも変換して返せる。
func >>> <T>(lhs: Process, rhs: (Output) -> T) -> T {

    let pipe = Pipe()
    lhs.standardOutput = pipe
    lhs.launch()

    return rhs(Output(fileHandle: pipe.fileHandleForReading))
}

Processクラスには直接手を加えず、カスタムオペレーションと補助的な型を追加することで、Processクラスをとてもわかりやすく、そして短い記述で使用できるようになりました。

オペレータの右側を関数にすることを思いついた時は思わず飛び上がりそうになりました。
こういうのを考えてるととても楽しくなりますね。

それではみなさん

let string = Process() <<< "/bin/echo" <<< ["Hello, World.\nこんにちは世界。"]
    | Process() <<< "/usr/bin/grep" <<< [","]
    | Process() <<< "/usr/bin/sed" <<< ["-e", "s/Hel/We/"]
    | Process() <<< "/usr/bin/sed" <<< ["-e", "s/,/ to New/"]
    | Process() <<< "/usr/bin/sed" <<< ["-e", "s/o/come/"]
    >>> { $0.string }

string.map { print($0) }
3
3
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
3
3