Best way to parse command-line parameters?
在scala中解析命令行参数的最佳方法是什么?我个人更喜欢不需要外部罐子的轻便的东西。
相关:
- 如何解析Java中的命令行参数?
- C++中有哪些参数解析器库?
- 解析C中命令行参数的最佳方法#
你务必用例不需要外部的解析器。Scala的模式匹配的消费参数允许在一个功能性的风格。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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) } } |
将打印,例如:
1 | Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2) |
这一版本只需要导入导出。容易提高在线(市采用A表)。
注意,这个方法也允许对多个命令行concatenation题元甚至超过两!
scopt / scopt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | val parser = new scopt.OptionParser[Config]("scopt") { head("scopt","3.x") opt[Int]('f',"foo") action { (x, c) => c.copy(foo = x) } text("foo is an integer property") opt[File]('o',"out") required() valueName("<file>") action { (x, c) => c.copy(out = x) } text("out is a required file property") opt[(String, Int)]("max") action { case ((k, v), c) => c.copy(libName = k, maxCount = v) } validate { x => if (x._2 > 0) success else failure("Value <max> must be >0") } keyValueName("<libname>","<max>") text("maximum count for <libname>") opt[Unit]("verbose") action { (_, c) => c.copy(verbose = true) } text("verbose is a flag") note("some notes. ") help("help") text("prints this usage text") arg[File]("<file>...") unbounded() optional() action { (x, c) => c.copy(files = c.files :+ x) } text("optional unbounded args") cmd("update") action { (_, c) => c.copy(mode ="update") } text("update is a command.") children( opt[Unit]("not-keepalive") abbr("nk") action { (_, c) => c.copy(keepalive = false) } text("disable keepalive"), opt[Boolean]("xyz") action { (x, c) => c.copy(xyz = x) } text("xyz is a boolean property") ) } // parser.parse returns Option[C] parser.parse(args, Config()) map { config => // do stuff } getOrElse { // arguments are bad, usage message will have been displayed } |
在以上generates使用下面的文字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | scopt 3.x Usage: scopt [update] [options] [<file>...] -f <value> | --foo <value> foo is an integer property -o <file> | --out <file> out is a required file property --max:<libname>=<max> maximum count for <libname> --verbose verbose is a flag some notes. --help prints this usage text <file>... optional unbounded args Command: update update is a command. -nk | --not-keepalive disable keepalive --xyz <value> xyz is a boolean property |
这是我目前使用的。使用干净的,没有太多的行李。 (disclaimer:保持在现在这个项目)
在意识到这是个问题,我问几小时前,但我想,它可能会帮助一些人,谁是在googling(像我)和点击本页。
看了很长promising扇贝为好。
特征("联quote github页):
- flag, single-value and multiple value options
- POSIX-style short option names (-a) with grouping (-abc)
- GNU-style long option names (--opt)
- Property arguments (-Dkey=value, -D key1=value key2=value)
- Non-string types of options and properties values (with extendable converters)
- Powerful matching on trailing args
- Subcommands
和一些实例代码(GitHub也从那一页):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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) |
对于相对简单的配置,我喜欢在参数上滑动。
1 2 3 4 5 6 7 8 |
命令行界面scala工具包(clist)
这也是我的!(不过比赛有点晚)
https://github.com/backuity/clist
与
1 2 3 4 5 6 7 8 9 | class Cat extends Command(description ="concatenate files and print on the standard output") { // type-safety: members are typed! so showAll is a Boolean var showAll = opt[Boolean](abbrev ="A", description ="equivalent to -vET") var numberNonblank = opt[Boolean](abbrev ="b", description ="number nonempty output lines, overrides -n") // files is a Seq[File] var files = args[Seq[File]](description ="files to concat") } |
以及一种简单的运行方法:
当然,您可以执行更多操作(多个命令、许多配置选项等),并且没有依赖关系。
最后,我将介绍一种独特的特性,即默认用法(对于多个命令,经常被忽略):
这是一个无耻的克隆largely我回答两个问题:《Java一样的话题。它会暂停,jewelcli Scala中冰友好的,它不需要JavaBeans风格的方法得到参数自动命名。
jewelcli Scala是一个友好的Java库为命令行解析,YIELDS干净的代码。它proxied辨别configured annotations接口与两个动态类型建立一个安全的API为你的命令行参数。
安:以
1 2 3 4 5 6 |
西安example of the usage:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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罐,两个目录为好。
编译和运行的实例在bash在Linux / Mac OS X /等。
1 2 | scalac -cp jewelcli-0.6.jar:. Person.scala Hello.scala scala -cp jewelcli-0.6.jar:. Hello --name="John Doe" --times=3 |
在编译和运行实例的Windows命令提示:
1 2 | scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3 |
行走的收益应该以下面的输出:
1 2 3 | Hello John Doe Hello John Doe Hello John Doe |
scala optparse应用程序
我认为scala optparse applicative是scala中功能最强大的命令行解析器库。
https://github.com/bmjames/scala-optparse-applicative
我来自Java世界,我喜欢Ags4J,因为它的简单、规范更可读(感谢注释),并产生了良好的格式化输出。
下面是我的示例片段:
规范1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | import org.kohsuke.args4j.{CmdLineException, CmdLineParser, Option} object CliArgs { @Option(name ="-list", required = true, usage ="List of Nutch Segment(s) Part(s)") var pathsList: String = null @Option(name ="-workdir", required = true, usage ="Work directory.") var workDir: String = null @Option(name ="-master", usage ="Spark master url") var masterUrl: String ="local[2]" } |
解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | //var args ="-listt in.txt -workdir out-2".split("") val parser = new CmdLineParser(CliArgs) try { parser.parseArgument(args.toList.asJava) } catch { case e: CmdLineException => print(s"Error:${e.getMessage} Usage: ") parser.printUsage(System.out) System.exit(1) } println("workDir :" + CliArgs.workDir) println("listFile :" + CliArgs.pathsList) println("master :" + CliArgs.masterUrl) |
关于无效参数
1 2 3 4 5 | Error:Option"-list" is required Usage: -list VAL : List of Nutch Segment(s) Part(s) -master VAL : Spark master url (default: local[2]) -workdir VAL : Work directory. |
我们的jcommander(也disclaimer:创造了它):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | object Main { object Args { @Parameter( names = Array("-f","--file"), description ="File to load. Can be specified multiple times.") var file: java.util.List[String] = null } def main(args: Array[String]): Unit = { new JCommander(Args, args.toArray: _*) for (filename <- Args.file) { val f = new File(filename) printf("file: %s ", f.getName) } } } |
如何解析没有外部依赖关系的参数。好问题!你可能对皮科利感兴趣。
Picocli是专门为解决这个问题而设计的:它是一个命令行解析框架,位于单个文件中,因此您可以将其包含在源代码形式中。这允许用户运行基于Picocli的应用程序,而不需要将Picocli作为外部依赖项。
它通过注释字段来工作,所以您只需编写很少的代码。快速总结:
- 强类型的所有内容-命令行选项和位置参数
- 支持POSIX集群短选项(因此它处理
和-xvfInputFile )-x -v -f InputFile - 一种arity模型,允许最小、最大和可变数量的参数,如
"1..*" 和"3..5" 。 - 流畅紧凑的API,最大限度地减少样板客户机代码
- 子命令
- ANSI颜色的使用帮助
使用帮助消息很容易通过注释进行自定义(无需编程)。例如:
(来源)
我忍不住再添加一个屏幕截图来显示什么样的使用帮助消息是可能的。使用帮助是你的应用程序的正面,所以要有创意,玩得开心!
免责声明:我创建了Picocli。欢迎反馈或提问。它是用Java编写的,但是让我知道在Scala中是否有使用它的问题,我会尝试解决它。
我喜欢joslim()的slide()方法,但不喜欢可变变量;)所以这里有一个不变的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | case class AppArgs( seed1: String, seed2: String, ip: String, port: Int ) object AppArgs { def empty = new AppArgs("","","", 0) } val args = Array[String]( "--seed1","akka.tcp://seed1", "--seed2","akka.tcp://seed2", "--nodeip","192.167.1.1", "--nodeport","2551" ) val argsInstance = args.sliding(2, 1).toList.foldLeft(AppArgs.empty) { case (accumArgs, currArgs) => currArgs match { case Array("--seed1", seed1) => accumArgs.copy(seed1 = seed1) case Array("--seed2", seed2) => accumArgs.copy(seed2 = seed2) case Array("--nodeip", ip) => accumArgs.copy(ip = ip) case Array("--nodeport", port) => accumArgs.copy(port = port.toInt) case unknownArg => accumArgs // Do whatever you want for this case } } |
在我们真的发现是游离在命令行解析图书馆的scala.tools.cmd scalac包。
看到http:/ / / /代码www.assembla.com斯卡拉日全食的Git工具链节点/ / / / / SRC Scala编译器/工具/命令吗?Rev = f59940622e32384b1e08939effd24e924a8ba8db
我把我的方法建立在最上面的答案上(来自Dave4420),并试图通过使其更通用来改进它。
它返回所有命令行参数的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | def argsToOptionMap(args:Array[String]):Map[String,String]= { def nextOption( argList:List[String], map:Map[String, String] ) : Map[String, String] = { val pattern ="--(\\w+)".r // Selects Arg from --Arg val patternSwitch ="-(\\w+)".r // Selects Arg from -Arg argList match { case Nil => map case pattern(opt) :: value :: tail => nextOption( tail, map ++ Map(opt->value) ) case patternSwitch(opt) :: tail => nextOption( tail, map ++ Map(opt->null) ) case string :: Nil => map ++ Map(string->null) case option :: tail => { println("Unknown option:"+option) sys.exit(1) } } } nextOption(args.toList,Map()) } |
例子:
1 2 |
给予:
在VC attempted pjotrp generalize"的解决方案,在一个城市,以列表的位置所需的关键符号,图(a ->关键标志符号和违约期权
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | def parseOptions(args: List[String], required: List[Symbol], optional: Map[String, Symbol], options: Map[Symbol, String]): Map[Symbol, String] = { args match { // Empty list case Nil => options // Keyword arguments case key :: value :: tail if optional.get(key) != None => parseOptions(tail, required, optional, options ++ Map(optional(key) -> value)) // Positional arguments case value :: tail if required != Nil => parseOptions(tail, required.tail, optional, options ++ Map(required.head -> value)) // Exit if an unknown argument is received case _ => printf("unknown argument(s): %s ", args.mkString(",")) sys.exit(1) } } def main(sysargs Array[String]) { // Required positional arguments by key in options val required = List('arg1, 'arg2) // Optional arguments by flag which map to a key in options val optional = Map("--flag1" -> 'flag1,"--flag2" -> 'flag2) // Default options that are passed in var defaultOptions = Map() // Parse options based on the command line args val options = parseOptions(sysargs.toList, required, optional, defaultOptions) } |
另一个scarg图书馆
这里的Scala命令行解析器,这是很容易使用。自动文本信息的格式,它的帮助,你converts题元desired开关两种类型。两个短的和长的角马POSIX风格,是开关的负载。支开关与所需的题元,可选题元,题元和多重价值。你可以在有限specify平衡表上可接受的值(特别是开关。大开关与程序的名称可以在命令行的便利。两个相似的期权的Ruby解析器的标准库。
有没有喜欢的Ruby类期权的分析器。开发人员必须使用他们,不会写一页,你为自己的脚本和比IP与pages长键槽的期权在一个适当的组织方式,因为其语法分析器。
有总是优先在Perl的方式"做的事情和Perl的getopt:长。
我工作在一个Scala执行它。一些早期的API,这样的感觉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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, )) |
所以
1 | $ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing |
将打印:
1 2 | higher order function version is 0.2 |
鸭的回报:
1 2 3 4 5 6 | remaining = Array("hello","world","--nothing") options = Map('flag -> true, 'string ->"mystring", 'int -> 7, 'double -> 3.14) |
《冰在GitHub项目主办getoptions斯卡拉。
我刚刚创建了简单的枚举
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 |
我知道解决方案有两个主要缺陷,可能会分散您的注意力:它消除了自由(即对其他库的依赖,您非常重视)和冗余(干燥原则,您只需键入一次选项名,作为scala程序变量,然后第二次将其作为命令行文本键入)。
我建议使用http://docopt.org/。有一个Scala端口,但是Java实现HTTPS://GITHUBCOM/Doopt/DooptJAVA工作得很好,并且看起来更好地保持了。下面是一个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import org.docopt.Docopt import scala.collection.JavaConversions._ import scala.collection.JavaConverters._ val doc = """ Usage: my_program [options] <input> Options: --sorted fancy sorting """.stripMargin.trim //def args ="--sorted test.dat".split("").toList var results = new Docopt(doc). parse(args()). map {case(key, value)=>key ->value.toString} val inputFile = new File(results("<input>")) val sorted = results("--sorted").toBoolean |
喜欢清洁的面貌,本代码。从这里gleaned讨论: www.scala-lang.org http:/ / / / / 4380老节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | object ArgParser { val usage =""" Usage: parser [-v] [-f file] [-s sopt] ... Where: -v Run verbosely -f F Set input file to F -s S Set Show option to S """ var filename: String ="" var showme: String ="" var debug: Boolean = false val unknown ="(^-[^\\s])".r val pf: PartialFunction[List[String], List[String]] = { case"-v" :: tail => debug = true; tail case"-f" :: (arg: String) :: tail => filename = arg; tail case"-s" :: (arg: String) :: tail => showme = arg; tail case unknown(bad) :: tail => die("unknown argument" + bad +" " + usage) } def main(args: Array[String]) { // if there are required args: if (args.length == 0) die() val arglist = args.toList val remainingopts = parseArgs(arglist,pf) println("debug=" + debug) println("showme=" + showme) println("filename=" + filename) println("remainingopts=" + remainingopts) } def parseArgs(args: List[String], pf: PartialFunction[List[String], List[String]]): List[String] = args match { case Nil => Nil case _ => if (pf isDefinedAt args) parseArgs(pf(args),pf) else args.head :: parseArgs(args.tail,pf) } def die(msg: String = usage) = { println(msg) sys.exit(1) } } |
我要继续干下去。我用一行简单的代码解决了这个问题。我的命令行参数如下:
1 | input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5 |
这将通过scala的本机命令行功能(从app或main方法)创建一个数组:
1 | Array("input--hdfs:/path/to/myData/part-00199.avro","output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5") |
然后,我可以使用这一行分析出默认的args数组:
1 |
它使用与命令行值关联的名称创建映射:
1 | Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5) |
然后,我可以访问代码中命名参数的值,它们在命令行上的显示顺序不再相关。我意识到这相当简单,没有上面提到的所有高级功能,但在大多数情况下似乎足够,只需要一行代码,不涉及外部依赖。
这是我的单程客轮
1 2 3 |
它删除了3个强制参数并给出了选项。整数被指定为臭名昭著的EDCOX1,0,Java选项,与前缀联合。您可以简单地分析二进制文件和整数
不需要导入任何内容。
这是我做的。它返回映射和列表的元组。列表用于输入,如输入文件名。地图用于开关/选项。
1 2 |
将返回
1 2 |
开关可以是"-t",哪个x将被设置为真,或者"-x 10",哪个x将被设置为"10"。其他的一切都会在名单上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | object OptParser { val map: Map[Symbol, Any] = Map() val list: List[Symbol] = List() def parse(args: Array[String]): (Map[Symbol, Any], List[Symbol]) = _parse(map, list, args.toList) private [this] def _parse(map: Map[Symbol, Any], list: List[Symbol], args: List[String]): (Map[Symbol, Any], List[Symbol]) = { args match { case Nil => (map, list) case arg :: value :: tail if (arg.startsWith("--") && !value.startsWith("--")) => _parse(map ++ Map(Symbol(arg.substring(2)) -> value), list, tail) case arg :: tail if (arg.startsWith("--")) => _parse(map ++ Map(Symbol(arg.substring(2)) -> true), list, tail) case opt :: tail => _parse(map, list :+ Symbol(opt), tail) } } } |
弗雷克利
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | package freecli package examples package command import java.io.File import freecli.core.all._ import freecli.config.all._ import freecli.command.all._ object Git extends App { case class CommitConfig(all: Boolean, message: String) val commitCommand = cmd("commit") { takesG[CommitConfig] { O.help --"help" :: flag --"all" -'a' -~ des("Add changes from all known files") :: O.string -'m' -~ req -~ des("Commit message") } :: runs[CommitConfig] { config => if (config.all) { println(s"Commited all ${config.message}!") } else { println(s"Commited ${config.message}!") } } } val rmCommand = cmd("rm") { takesG[File] { O.help --"help" :: file -~ des("File to remove from git") } :: runs[File] { f => println(s"Removed file ${f.getAbsolutePath} from git") } } val remoteCommand = cmd("remote") { takes(O.help --"help") :: cmd("add") { takesT { O.help --"help" :: string -~ des("Remote name") :: string -~ des("Remote url") } :: runs[(String, String)] { case (s, u) => println(s"Remote $s $u added") } } :: cmd("rm") { takesG[String] { O.help --"help" :: string -~ des("Remote name") } :: runs[String] { s => println(s"Remote $s removed") } } } val git = cmd("git", des("Version control system")) { takes(help --"help" :: version --"version" -~ value("v1.0")) :: commitCommand :: rmCommand :: remoteCommand } val res = runCommandOrFail(git)(args).run } |
这将生成以下用法:
用法
大家都在这里发布了自己的解决方案,因为我想为用户写些更容易的东西:https://gist.github.com/gwenzek/78355526e476e08bb34d
gist包含一个代码文件,加上一个测试文件和一个复制到这里的简短示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import ***.ArgsOps._ object Example { val parser = ArgsOpsParser("--someInt|-i" -> 4,"--someFlag|-f","--someWord" ->"hello") def main(args: Array[String]){ val argsOps = parser <<| args val someInt : Int = argsOps("--someInt") val someFlag : Boolean = argsOps("--someFlag") val someWord : String = argsOps("--someWord") val otherArgs = argsOps.args foo(someWord, someInt, someFlag) } } |
不存在强制变量在某些边界内的奇特选项,因为我觉得解析器不是最好的选择。
注意:对于给定的变量,您可以有任意多的别名。
可怜的人快速和肮脏的一行分析键=值对:
1 2 3 4 5 |