こたらの日記

Swift製コマンドラインツールで引数をパースする

Cover Image for Swift製コマンドラインツールで引数をパースする

前回はSwift製コマンドラインツールの始め方について書きました。 今回はその続きで、引数の受け取りについて書きます。

引数の受け取り方

CommandLine.argumentsを使うと引数をSwiftコード内で取り扱えます。 Swiftの以下のコードをターミナルから呼び出してみます。

print(CommandLine.arguments)

実行結果は以下になります。

$ swift run
[3/3] Linking Swift-CLI
[".build/x86_64-apple-macosx/debug/Swift-CLI"]
$ swift run Swift-CLI hello
[".build/x86_64-apple-macosx/debug/Swift-CLI", "hello"]
$ swift run Swift-CLI hello world
[".build/x86_64-apple-macosx/debug/Swift-CLI", "hello", "world"]
$ swift run Swift-CLI "hello world"
[".build/x86_64-apple-macosx/debug/Swift-CLI", "hello world"]

CommandLine.argumentsの型は[String]となっていて、CommandLine.arguments[1]以降が受け取った引数です。

こんなコードで引数を使った処理が書けます。

let arguments = CommandLine.arguments.dropFirst()
if let argument = arguments.first {
    // ここで引数を使った処理
}

引数が1つくらいならこれでも問題ないんですが、オプションを扱うようなコマンドラインツールを作ろうとすると

  • -m --message のように、- -- つきでキーワードが指定できる
  • フラグのようにオプションの指定だけする場合(--verbose)と、オプションに値が入る場合(--message "this is input message")がある
  • -al のように複数のオプションを繋げて使える

などを実装することになるので、オプションのパースを自前で作るのは少し面倒になります。

swift-argument-parser を使う

Apple製の swift-argument-parser を使って、オプションをパースしてみます。

import ArgumentParser

struct Repeat: ParsableCommand {
    @Flag(help: "Include a counter with each repetition.")
    var includeCounter = false

    @Option(name: .shortAndLong, help: "The number of times to repeat 'phrase'.")
    var count: Int?

    @Argument(help: "The phrase to repeat.")
    var phrase: String

    mutating func run() throws {
        let repeatCount = count ?? .max

        for i in 1...repeatCount {
            if includeCounter {
                print("\(i): \(phrase)")
            } else {
                print(phrase)
            }
        }
    }
}

Repeat.main()

このコードの実行結果は下記になります。

$ swift run Swift-CLI "hello world" --count 1
hello world
$ swift run Swift-CLI "hello world" -c 3
hello world
hello world
$ swift run Swift-CLI "hello world" -c 3 --include-counter
1: hello world
2: hello world
3: hello world

swift-argument-parser は propertyWrapper で @Argument @Option @Flag などが定義されているのが特徴で、直感的で使いやすい印象を持ちました。 swift-formatSwiftLint でも swift-argument-parser が使われるようになっているようです。

引数やオプションをパースするのに便利なライブラリは Carthage で使われている Commandant や XcodeGen で使われている SwiftCLI、他にも Commander などがあるので、好みで使うのがいいと思います。