在Scala中解析命令行参数的最佳方法是什么? 我个人更喜欢一些不需要外部罐子的轻量级的东西。

相关:

我如何解析Java中的命令行参数? c++有哪些参数解析器库? 在c#中解析命令行参数的最佳方法


当前回答

我从来都不喜欢ruby那样的选项解析器。大多数使用它们的开发人员从来没有为他们的脚本编写合适的手册页,并且由于解析器的原因,最终会以没有以适当的方式组织的数页长的选项结束。

我一直喜欢用Perl的Getopt::Long来做事情。

我正在研究它的scala实现。早期的API看起来是这样的:

def print_version() = () => println("version is 0.2")

def main(args: Array[String]) {
  val (options, remaining) = OptionParser.getOptions(args,
    Map(
      "-f|--flag"       -> 'flag,
      "-s|--string=s"   -> 'string,
      "-i|--int=i"      -> 'int,
      "-f|--float=f"    -> 'double,
      "-p|-procedure=p" -> { () => println("higher order function" }
      "-h=p"            -> { () => print_synopsis() }
      "--help|--man=p"  -> { () => launch_manpage() },
      "--version=p"     -> print_version,
    ))

像这样调用脚本:

$ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing

将打印:

higher order function
version is 0.2

并返回:

remaining = Array("hello", "world", "--nothing")

options = Map('flag   -> true,
              'string -> "mystring",
              'int    -> 7,
              'double -> 3.14)

该项目托管在github scala-getoptions中。

其他回答

我只是创建了简单的枚举

val args: Array[String] = "-silent -samples 100 -silent".split(" +").toArray
                                              //> args  : Array[String] = Array(-silent, -samples, 100, -silent)
object Opts extends Enumeration {

    class OptVal extends Val {
        override def toString = "-" + super.toString
    }

    val nopar, silent = new OptVal() { // boolean options
        def apply(): Boolean = args.contains(toString)
    }

    val samples, maxgen = new OptVal() { // integer options
        def apply(default: Int) = { val i = args.indexOf(toString) ;  if (i == -1) default else args(i+1).toInt}
        def apply(): Int = apply(-1)
    }
}

Opts.nopar()                              //> res0: Boolean = false
Opts.silent()                             //> res1: Boolean = true
Opts.samples()                            //> res2: Int = 100
Opts.maxgen()                             //> res3: Int = -1

我知道这个解决方案有两个主要的缺陷可能会让你分心:它消除了自由(即对其他库的依赖,你非常重视)和冗余(DRY原则,你只输入一次选项名称,作为Scala程序变量,并在第二次输入命令行文本时消除它)。

穷人用来解析键=值对的快速而肮脏的一行代码:

def main(args: Array[String]) {
    val cli = args.map(_.split("=") match { case Array(k, v) => k->v } ).toMap
    val saveAs = cli("saveAs")
    println(saveAs)
}

这在很大程度上是我对同一主题的Java问题的答案的无耻克隆。事实证明,JewelCLI是scala友好的,因为它不需要JavaBean样式的方法来获得自动参数命名。

JewelCLI是一个scala友好的Java库,用于命令行解析,生成干净的代码。它使用带有注解的代理接口来动态地为命令行参数构建类型安全的API。

一个参数接口Person.scala的例子:

import uk.co.flamingpenguin.jewel.cli.Option

trait Person {
  @Option def name: String
  @Option def times: Int
}

参数接口Hello.scala的用法示例:

import uk.co.flamingpenguin.jewel.cli.CliFactory.parseArguments
import uk.co.flamingpenguin.jewel.cli.ArgumentValidationException

object Hello {
  def main(args: Array[String]) {
    try {
      val person = parseArguments(classOf[Person], args:_*)
      for (i <- 1 to (person times))
        println("Hello " + (person name))
    } catch {
      case e: ArgumentValidationException => println(e getMessage)
    }
  }
}

将上述文件的副本保存到一个目录中,并将JewelCLI 0.6 JAR下载到该目录中。

在Linux/Mac OS X/etc / Bash中编译并运行示例:

scalac -cp jewelcli-0.6.jar:. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar:. Hello --name="John Doe" --times=3

在Windows命令提示符中编译并运行示例:

scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3

运行该示例应该产生以下输出:

Hello John Doe
Hello John Doe
Hello John Doe

我再多说一点。我用一行简单的代码解决了这个问题。我的命令行参数是这样的:

input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5

这将通过Scala的本机命令行功能(从App或main方法)创建一个数组:

Array("input--hdfs:/path/to/myData/part-00199.avro", "output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5")

然后我可以使用这一行来解析出默认的args数组:

val nArgs = args.map(x=>x.split("--")).map(y=>(y(0),y(1))).toMap

它创建了一个与命令行值关联的名称映射:

Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5)

然后,我可以在代码中访问命名参数的值,它们在命令行上出现的顺序不再相关。我意识到这相当简单,没有上面提到的所有高级功能,但在大多数情况下似乎足够了,只需要一行代码,并且不涉及外部依赖关系。

我刚刚在scalac的scala.tools.cmd包中找到了一个广泛的命令行解析库。

参见http://www.assembla.com/code/scala-eclipse-toolchain/git/nodes/src/compiler/scala/tools/cmd?rev=f59940622e32384b1e08939effd24e924a8ba8db