在Scala中解析命令行参数的最佳方法是什么? 我个人更喜欢一些不需要外部罐子的轻量级的东西。
相关:
我如何解析Java中的命令行参数? c++有哪些参数解析器库? 在c#中解析命令行参数的最佳方法
在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中。
其他回答
我知道这个问题是很久以前问过的,但我认为它可能会帮助一些人,谁是谷歌周围(像我),并点击这个页面。
扇贝看起来也很有希望。
特点(引用自链接的github页面):
标志,单值和多值选项 posix风格的短选项名称(-a)带有分组(-abc) gnu风格的长选项名称(——opt) 属性参数(-Dkey=value, -D key1=value key2=value) 非字符串类型的选项和属性值(带有可扩展转换器) 对尾随参数的强大匹配 子命令
还有一些示例代码(也来自Github页面):
import org.rogach.scallop._;
object Conf extends ScallopConf(List("-c","3","-E","fruit=apple","7.2")) {
// all options that are applicable to builder (like description, default, etc)
// are applicable here as well
val count:ScallopOption[Int] = opt[Int]("count", descr = "count the trees", required = true)
.map(1+) // also here work all standard Option methods -
// evaluation is deferred to after option construction
val properties = props[String]('E')
// types (:ScallopOption[Double]) can be omitted, here just for clarity
val size:ScallopOption[Double] = trailArg[Double](required = false)
}
// that's it. Completely type-safe and convenient.
Conf.count() should equal (4)
Conf.properties("fruit") should equal (Some("apple"))
Conf.size.get should equal (Some(7.2))
// passing into other functions
def someInternalFunc(conf:Conf.type) {
conf.count() should equal (4)
}
someInternalFunc(Conf)
对于大多数情况,您不需要外部解析器。Scala的模式匹配允许以函数式风格使用参数。例如:
object MmlAlnApp {
val usage = """
Usage: mmlaln [--min-size num] [--max-size num] filename
"""
def main(args: Array[String]) {
if (args.length == 0) println(usage)
val arglist = args.toList
type OptionMap = Map[Symbol, Any]
def nextOption(map : OptionMap, list: List[String]) : OptionMap = {
def isSwitch(s : String) = (s(0) == '-')
list match {
case Nil => map
case "--max-size" :: value :: tail =>
nextOption(map ++ Map('maxsize -> value.toInt), tail)
case "--min-size" :: value :: tail =>
nextOption(map ++ Map('minsize -> value.toInt), tail)
case string :: opt2 :: tail if isSwitch(opt2) =>
nextOption(map ++ Map('infile -> string), list.tail)
case string :: Nil => nextOption(map ++ Map('infile -> string), list.tail)
case option :: tail => println("Unknown option "+option)
exit(1)
}
}
val options = nextOption(Map(),arglist)
println(options)
}
}
将打印,例如:
Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)
这个版本只需要一个文件。很容易改进(通过使用列表)。
还要注意,这种方法允许连接多个命令行参数——甚至超过两个!
如何在没有外部依赖的情况下解析参数。好问题!你可能会对picocli感兴趣。
Picocli是专门为解决问题而设计的:它是一个文件中的命令行解析框架,因此可以以源代码形式包含它。这允许用户运行基于picocli的应用程序,而不需要将picocli作为外部依赖项。
它通过注释字段来工作,因此您只需编写很少的代码。快速总结:
强类型的一切-命令行选项以及位置参数 支持POSIX集群短选项(因此它处理<命令> -xvfInputFile以及<命令> -x -v -f InputFile) 一个允许最小、最大和可变数量参数的arity模型,例如,“1..3 . . *”、“5” 流畅和紧凑的API,以尽量减少样板客户端代码 子命令 使用ANSI颜色的帮助
使用帮助消息很容易使用注释进行定制(无需编程)。例如:
(源)
我忍不住又加了一张截图来展示使用帮助信息的类型。使用帮助是应用程序的门面,所以要有创意,玩得开心!
声明:我创建了picocli。欢迎反馈或提问。它是用java编写的,但如果在scala中使用它有任何问题,请告诉我,我会尝试解决它。
这是我的一行字
def optArg(prefix: String) = args.drop(3).find { _.startsWith(prefix) }.map{_.replaceFirst(prefix, "")}
def optSpecified(prefix: String) = optArg(prefix) != None
def optInt(prefix: String, default: Int) = optArg(prefix).map(_.toInt).getOrElse(default)
它删除了3个强制参数,并给出了选项。整数被指定为臭名昭著的-Xmx<size> java选项,加上前缀。您可以像这样简单地解析二进制和整数
val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)
不需要进口任何东西。
我再多说一点。我用一行简单的代码解决了这个问题。我的命令行参数是这样的:
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)
然后,我可以在代码中访问命名参数的值,它们在命令行上出现的顺序不再相关。我意识到这相当简单,没有上面提到的所有高级功能,但在大多数情况下似乎足够了,只需要一行代码,并且不涉及外部依赖关系。