解析命令行参数的最佳方法?[关闭]


237

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

有关:

Answers:


228

在大多数情况下,您不需要外部解析器。Scala的模式匹配允许以功能样式使用args。例如:

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)

此版本仅包含一个文件。易于改进(通过使用列表)。

还要注意,这种方法允许串联多个命令行参数-甚至可以超过两个!


4
isSwitch只是检查第一个字符是破折号'
-'– pjotrp

6
nextOption不是该函数的好名字。它是一个返回映射的函数-它是递归的事实是实现细节。这就像max为集合编写函数并调用它一样,nextMax因为您使用显式递归编写了该函数。为什么不直接打电话呢optionMap
itsbruce

4
@itsbruce我只想添加/修改您的观点-从可读性/可维护性出发,使用其中定义listToOptionMap(lst:List[String])的函数进行nextOption定义是最“恰当的” ,最后一行说return nextOption(Map(), lst)。就是说,我不得不承认,我在这段时间里所做出的捷径比答案中的捷径要严重得多。
tresbot

6
上面的代码中的@theMadKing exit(1)可能需要是sys.exit(1)
tresbot 2015年

3
我喜欢你的解决方案。下面就来处理多个修改file参数: case string :: tail => { if (isSwitch(string)) { println("Unknown option: " + string) sys.exit(1) } else nextOption(map ++ Map('files -> (string :: map('files).asInstanceOf[List[String]])), tail)。该地图还需要一个默认值Nil,即 val options = nextOption(Map() withDefaultValue Nil, args.toList)asInstanceOf由于OptionMap值是类型,我不喜欢使用该方法Any。有更好的解决方案吗?
Mauro Lacy

196

选择/侦察

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.\n")

  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
}

上面生成了以下用法文本:

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

这是我目前使用的。清洁使用,无需太多行李。(免责声明:我现在维护这个项目)


6
我更喜欢构建器模式DSL,因为它可以将参数构造委托给模块。
Daniel C. Sobral

3
注意:与所示不同,scopt不需要那么多类型注释。
Blaisorblade

9
如果您使用它来解析用于火花作业的arg,请注意它们不能很好地配合使用。从字面上看,我尝试过的任何事情都无法通过scopt提交:-(
jbrown

4
@BirdJaguarIV如果spark使用scopt,那可能是问题所在-jar中的版本冲突或其他问题。我改用扇贝做火花工作,没有任何问题。
jbrown 2015年

12
具有讽刺意味的是,尽管该库自动生成了良好的CLI文档,但是代码看起来比Brainf * ck更好。
乔纳森·诺伊菲尔德

58

我意识到这个问题是在不久前提出的,但我认为这可能会帮助一些像我一样四处搜寻的人找到该页面。

扇贝看起来也很有前途。

功能(从链接的github页面引用):

  • 标志,单值和多值选项
  • 带有分组(-abc)的POSIX样式的简短选项名称(-a)
  • GNU样式的长选项名称(--opt)
  • 属性参数(-Dkey =值,-D key1 =值key2 =值)
  • 选项和属性值的非字符串类型(带有可扩展转换器)
  • 尾随args的强大匹配
  • 子指令

还有一些示例代码(同样来自该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)

4
扇贝在功能上将其余部分递减。可耻的是,“第一答案胜出”的通常趋势已将其推到了榜首:(
samthebest

我同意。万一@Eugene Yokota遗漏了笔记,请在此处发表评论。检查此博客扇贝
Pramit

1
提到scopt时遇到的问题是“它看起来不错,但是无法解析带有参数列表的选项(即-a 1 2 3)。而且您无法扩展它来获取那些列表(除了分叉lib)。” 但这不再是真的,请参阅github.com/scopt/scopt#options
Alexey Romanov

2
与scopt相比,这更直观且样板更少。不再(x, c) => c.copy(xyz = x) 有争议
WeiChing林炜清

43

我喜欢了论据较为简单的结构。

var name = ""
var port = 0
var ip = ""
args.sliding(2, 2).toList.collect {
  case Array("--ip", argIP: String) => ip = argIP
  case Array("--port", argPort: String) => port = argPort.toInt
  case Array("--name", argName: String) => name = argName
}

2
聪明。不过,仅当每个arg也都指定一个值时,该方法才有效,对吧?
布伦特浮士德2015年

2
应该不是args.sliding(2, 2)吗?
2016年

1
应该不是var port = 0吗?
swdev

17

命令行界面Scala工具包(CLIST)

这里也是我的!(虽然在游戏中有点晚)

https://github.com/backuity/clist

相对而言,scopt这是完全可变的...但是请稍等!这给了我们一个很好的语法:

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")
}

和运行它的简单方法:

Cli.parse(args).withCommand(new Cat) { case cat =>
    println(cat.files)
}

当然,您可以做更多的事情(多命令,许多配置选项...),并且没有依赖性。

最后,我将介绍一种独特的功能,即默认用法(通常在多命令中被忽略): 列表


有验证吗?
KF

是的,它确实可以(例如,请参阅github.com/backuity/clist/blob/master/demo/src/main/scala/…)。虽然没有记载... PR?:)
Bruno Bieth's

试了一下,很方便。我以前使用过scopt,但我仍然不习惯将验证一起添加,而不仅仅是在每个参数的定义中。但这对我很好。并定义具有不同特征的不同参数和验证,然后在不同情况下将它们组合在一起,这确实很有帮助。当不方便重用参数时,我在scopt中遭受了很多麻烦。谢谢您的回复!
KF

命令行参数(见的反序列化过程中执行的大多数验证读取),所以如果你可以通过类型定义验证约束(即PasswordHex......),那么你可以利用这一点。
布鲁诺·比斯

13

这很大程度上是我对同一主题的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

您可能会注意到的一个有趣之处是(args:_ *)。从Scala调用Java varargs方法需要这样做。这是一个解决方案,我从中学到daily-scala.blogspot.com/2009/11/varargs.html杰西Eichar的优秀日报斯卡拉博客。我强烈推荐Daily Scala :)
Alain O'Dea 2010年

12

如何在没有外部依赖性的情况下解析参数。好问题!您可能对picocli感兴趣。

Picocli是专门为解决问题而设计的:它是单个文件中的命令行解析框架,因此您可以将其包含在源代码中。这使用户可以运行基于picocli的应用程序,而无需将picocli作为外部依赖项

它通过注释字段来工作,因此您只需编写很少的代码。快速总结:

  • 严格键入所有内容-命令行选项以及位置参数
  • 支持POSIX集群简短选项(因此它也可以处理<command> -xvfInputFile<command> -x -v -f InputFile
  • 允许最小,最大和可变数量的参数的Arity模型,例如"1..*""3..5"
  • 流利而紧凑的API可最大程度地减少样板客户代码
  • 子指令
  • ANSI颜色的使用帮助

使用帮助消息很容易通过注释进行自定义(无需编程)。例如:

扩展使用帮助信息来源

我忍不住要再添加一个屏幕截图,以显示可能的使用帮助信息。使用帮助是您应用程序的面目,因此请发挥创造力并尽情玩乐!

picocli演示

免责声明:我创建了picocli。反馈或问题非常欢迎。它是用Java编写的,但是让我知道在scala中使用它是否有任何问题,我将尝试解决。


1
为什么要下票?这是我所知道的唯一一个专门用于解决OP中提到的问题的库:如何避免添加依赖项。
雷姆波马(Remko Popma)'17

“鼓励应用程序作者包括它”。辛苦了
keos

你有scala的例子吗?
CruncherBigData

1
我已经开始为其他JVM语言创建示例:github.com/remkop/picocli/issues/183欢迎反馈和贡献!
Remko Popma '17

11

我来自Java世界,我喜欢args4j,因为它的简单规范更易读(由于使用了批注),并且可以生成格式正确的输出。

这是我的示例片段:

规范

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]"

}

解析

//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}\n Usage:\n")
    parser.printUsage(System.out)
    System.exit(1)
}
println("workDir  :" + CliArgs.workDir)
println("listFile :" + CliArgs.pathsList)
println("master   :" + CliArgs.masterUrl)

关于无效的论点

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.


8

还有JCommander(免责声明:我创建了它):

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\n", f.getName)
    }
  }
}

2
我喜欢这一个。那些“纯scala”解析器缺少简洁的语法
tactoth

@tactoth检查这一项,它有一个明显的语法:stackoverflow.com/questions/2315912/...
布鲁诺Bieth

6

我喜欢joslinm的slide()方法,而不是可变的vars;),所以这是一种不可变的方法:

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
  }
}


3

我尝试通过获取所需位置键符号,标志映射->键符号和默认选项的列表来概括@pjotrp的解决方案:

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\n", 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)
}

我更新了这段代码以处理标志(不仅是带有值的选项),而且还处理了定义长/短格式的选项/标志的操作。例如-f|--flags。看看gist.github.com/DavidGamba/b3287d40b019e498982c,如果愿意,可以随时更新答案。我可能会制作每个Map和选项,因此您只能使用命名参数传递所需的内容。
DavidG 2014年

3

我的方法基于最佳答案(来自dave4420),并尝试通过使其更具通用性来加以改进。

它返回Map[String,String]所有命令行参数中的一个。您可以查询所需的特定参数(例如使用.contains),或将值转换为所需的类型(例如使用toInt)。

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())
}

例:

val args=Array("--testing1","testing1","-a","-b","--c","d","test2")
argsToOptionMap( args  )

给出:

res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)


2

这是一个易于使用的scala命令行解析器。它会自动设置帮助文本的格式,并将转换参数转换为所需的类型。支持短POSIX和长GNU样式的开关。支持带有必需参数,可选参数和多个值参数的开关。您甚至可以为特定开关指定可接受值的有限列表。为了方便起见,可以在命令行上缩写长开关名称。类似于Ruby标准库中的选项解析器。


2

我从来没有像选项解析器一样喜欢ruby。使用它们的大多数开发人员从不为其脚本编写适当的手册页,而最终由于其解析器而导致页面冗长的选项无法以正确的方式进行组织。

我一直喜欢Perl的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

$ 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中


2

我刚刚创建了简单的枚举

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程序)变量并消除它,第二次键入为命令行文本)。


2

我建议使用http://docopt.org/。有一个scala端口,但是Java实现https://github.com/docopt/docopt.java可以正常工作,并且似乎可以更好地维护。这是一个例子:

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

2

这是我煮的 它返回地图和列表的元组。列表用于输入,例如输入文件名。映射用于开关/选项。

val args = "--sw1 1 input_1 --sw2 --sw3 2 input_2 --sw4".split(" ")
val (options, inputs) = OptParser.parse(args)

将返回

options: Map[Symbol,Any] = Map('sw1 -> 1, 'sw2 -> true, 'sw3 -> 2, 'sw4 -> true)
inputs: List[Symbol] = List('input_1, 'input_2)

开关可以是“ --t”(将x设置为true),也可以是“ --x 10”(将x设置为“ 10”)。其他所有内容将最终显示在列表中。

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

我喜欢这段代码的简洁外观...从此处的讨论中收集:http : //www.scala-lang.org/old/node/4380

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 + "\n" + 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

正如每个人都说的,这是我自己的解决方案,因为我想要为用户编写更简单的东西:https : //gist.github.com/gwenzek/78355526e476e08bb34d

要点包含一个代码文件,以及一个测试文件和一个复制在此处的简短示例:

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

我要继续下去。我用简单的代码行解决了这个问题。我的命令行参数如下所示:

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)

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


1

这是我的1班轮

    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)

无需导入任何东西。


0

可怜的人的快速和肮脏的一线分析键=值对:

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

0

freecli

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
}

这将产生以下用法:

用法

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.