Best way to parse command-line parameters?

151,212

Solution 1

scopt/scopt

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
}

The above generates the following usage text:

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

This is what I currently use. Clean usage without too much baggage. (Disclaimer: I now maintain this project)

Solution 2

For most cases you do not need an external parser. Scala's pattern matching allows consuming args in a functional style. For example:

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

will print, for example:

Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)

This version only takes one infile. Easy to improve on (by using a List).

Note also that this approach allows for concatenation of multiple command line arguments - even more than two!

Solution 3

I realize that the question was asked some time ago, but I thought it might help some people, who are googling around (like me), and hit this page.

Scallop looks quite promising as well.

Features (quote from the linked github page):

  • 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

And some example code (also from that Github page):

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)

Solution 4

I like sliding over arguments for relatively simple configurations.

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
}

Solution 5

Command Line Interface Scala Toolkit (CLIST)

here is mine too! (a bit late in the game though)

https://github.com/backuity/clist

As opposed to scopt it is entirely mutable... but wait! That gives us a pretty nice syntax:

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

And a simple way to run it:

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

You can do a lot more of course (multi-commands, many configuration options, ...) and has no dependency.

I'll finish with a kind of distinctive feature, the default usage (quite often neglected for multi commands): clist

Share:
151,212

Related videos on Youtube

Eugene Yokota
Author by

Eugene Yokota

Hi, I'm Eugene (eed3si9n). I am a software engineer and an open source contributor mostly around Scala. As the core maintainer of sbt, a build tool used in Scala community, I like helping debug and explain sbt. Other projects I contribute to: scalaxb, an XML databinding tool for Scala (author) treehugger.scala (author) scopt/scopt (maintainer) Twitter: @eed3si9n Github: @eed3si9n

Updated on March 18, 2020

Comments

  • Eugene Yokota
    Eugene Yokota over 4 years

    What's the best way to parse command-line parameters in Scala? I personally prefer something lightweight that does not require external jar.

    Related:

  • Daniel C. Sobral
    Daniel C. Sobral over 14 years
    I like the builder pattern DSL much better, because it enables delegation of parameter construction to modules.
  • ThanksForYourHelp
    ThanksForYourHelp almost 14 years
    One piece of fun in this you may notice is the (args : _*). Calling Java varargs methods from Scala requires this. This is a solution I learned from daily-scala.blogspot.com/2009/11/varargs.html on Jesse Eichar's excellent Daily Scala blog. I highly recommend Daily Scala :)
  • Blaisorblade
    Blaisorblade almost 12 years
    Note: unlike shown, scopt doesn't need that many type annotations.
  • MCP
    MCP almost 11 years
    Very nice. I am wondering what exactly the "isSwitch" check for and why it's being used in the 4th case statement? Thanks for the great example!
  • pjotrp
    pjotrp almost 11 years
    isSwitch simply checks for the first character being a dash '-'
  • DavidGamba
    DavidGamba about 10 years
    I updated this piece of code to handle flags (not just options with values) and also to mandle defining the option/flag with short and long forms. e.g. -f|--flags. Take a look at gist.github.com/DavidGamba/b3287d40b019e498982c and feel free to update the answer if you like it. I will probably will make every Map and option so you can only pass what you will need with named arguments.
  • Jim Pivarski
    Jim Pivarski almost 10 years
    scopt and scallop are available in Maven Central; the others don't seem to be (I couldn't find them). Also, it looks like paulp/optional has become alexy/optional.
  • Erik Kaplun
    Erik Kaplun over 9 years
    does it have any examples/doc in addition to what's in the README?
  • Rollen Holt
    Rollen Holt over 9 years
    does scopt project has some document?
  • appbootup
    appbootup over 9 years
    Just wanted to point out that Argot is definitely on Maven Central (have been using it for a few years now).
  • gpampara
    gpampara over 9 years
    It does, check the examples in the test code
  • jbrown
    jbrown over 9 years
    If you're using this for parsing args for a spark job, be warned that they don't play nicely together. Literally nothing I tried could get spark-submit to work with scopt :-(
  • beardc
    beardc about 9 years
    @jbrown I found it interesting that one of the top search results for scopt usage in github is in Spark. But I have no experience with either library, and no idea how incompatible the two are.
  • jbrown
    jbrown about 9 years
    @BirdJaguarIV If spark uses scopt that was probably the problem - conflicting versions in the jar or something. I use scallop with spark jobs instead and haven't had any problems.
  • Coder Guy
    Coder Guy about 9 years
    Ironically though this library automagically generates good CLI documentation, the code looks little better than brainf*ck.
  • theMadKing
    theMadKing about 9 years
    I am getting a: cannot resolve symbol exit. Can anyone help out as to why this might be happening?
  • Brent Faust
    Brent Faust about 9 years
    Clever. Only works if every arg also specifies a value, though, right?
  • tactoth
    tactoth almost 9 years
    i like this one. those 'pure scala' parsers lack a clean syntax
  • samthebest
    samthebest almost 9 years
    Scallop pwns the rest hands down in terms of features. Shame the usual SO trend of "first answer wins" has pushed this down the list :(
  • itsbruce
    itsbruce almost 9 years
    nextOption is not a good name for the function. It's a function that returns a map - the fact that it is recursive is an implementation detail. It's like writing a max function for a collection and calling it nextMax simply because you wrote it with explicit recursion. Why not just call it optionMap?
  • tresbot
    tresbot almost 9 years
    @itsbruce I just want to add to/modify your point--it would be most "proper" from a readability/maintainability to define listToOptionMap(lst:List[String]) with the function nextOption defined within that, with a final line saying return nextOption(Map(), lst). That said, I have to confess that I've made much more egregious shortcuts in my time than the one in this answer.
  • tresbot
    tresbot almost 9 years
    @theMadKing in the code above exit(1) may need to be sys.exit(1)
  • Pramit
    Pramit over 8 years
    I agree. Leaving a comment here just incase @Eugene Yokota missed to take a note. Check this blog out scallop
  • Bruno Bieth
    Bruno Bieth over 8 years
    @tactoth check this one, it has a clear syntax: stackoverflow.com/questions/2315912/…
  • Andriy Drozdyuk
    Andriy Drozdyuk over 8 years
    What are the import statements? My IDE can't find "Config".
  • Eugene Yokota
    Eugene Yokota over 8 years
    @drozzy See github.com/scopt/scopt#usage "Either case, first you need a case class that represents the configuration"
  • Alexey Romanov
    Alexey Romanov about 8 years
    The problem it menthions with scopt is "It looks good, but is unable to parse options, which take a list of arguments (i.e. -a 1 2 3). And you have no way to extend it to get those lists (except forking the lib)." but this is no longer true, see github.com/scopt/scopt#options.
  • Jack Leow
    Jack Leow almost 8 years
    @jbrown Not sure if this is still relevant to you, but I just tried scopt with my Spark (2.0.0) application, and it worked just fine for me.
  • Mauro Lacy
    Mauro Lacy over 7 years
    I like your solution. Here's the modification to handle multiple file parameters: 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). The map also needs a default value of Nil, i.e. val options = nextOption(Map() withDefaultValue Nil, args.toList). What I don't like is having to resort to asInstanceOf, due to the OptionMap values being of type Any. Is there a better solution?
  • Jorge
    Jorge over 7 years
    Here is an addition to handle a flag, i.e., a command line argument with no associated value: case "--verbose" :: tail => nextOption(map ++ Map('verbose -> true), tail). Later we can get a boolean whether the flag is present or not with: val verbose = options.contains('verbose).
  • Arunav Sanyal
    Arunav Sanyal over 7 years
    When someone writes an example, its nicer to provide a version that actually compiles. How would I know what opt stands for and what is the package definition for the same?
  • m01
    m01 over 7 years
    Should it not be args.sliding(2, 2)?
  • acumartini
    acumartini over 7 years
    I prefer scallop (github.com/scallop/scallop). Much simpler implementation, cleaner docs. No offense to the maintainers of scopt, I just prefer KISS.
  • WeiChing 林煒清
    WeiChing 林煒清 over 7 years
    this is more intuitive and less boilerplate than scopt. no more (x, c) => c.copy(xyz = x) in scopt
  • Antonin
    Antonin over 7 years
    I do not get the case string :: opt2 :: tail if isSwitch(opt2) : would it mean that the filename is provided before another option "opt2" ? when is this opt2 parsed ?
  • Remko Popma
    Remko Popma about 7 years
    Why the downvote? This is the only library I'm aware of that is specifically designed to address the problem mentioned in the OP: how to avoid adding a dependency.
  • swdev
    swdev about 7 years
    Should it not be var port = 0 ?
  • cuz
    cuz almost 7 years
    "encourage application authors to include it". Good work.
  • CruncherBigData
    CruncherBigData almost 7 years
    do you have scala examples?
  • Remko Popma
    Remko Popma almost 7 years
    I've started to create examples for other JVM languages: github.com/remkop/picocli/issues/183 Feedback and contributions welcome!
  • K F
    K F over 6 years
    Does it have validation?
  • Bruno Bieth
    Bruno Bieth over 6 years
    Yes it does (see github.com/backuity/clist/blob/master/demo/src/main/scala/… for an example). It's not documented though... PR? :)
  • K F
    K F over 6 years
    Tried it, pretty convenient. I used scopt before, I'm still not get used to add validations together, but not just in each parameter's definition. But it works fine with me. And defining different parameters and validations in different traits, then combining them in different cases, that's real helpful. I suffered a lot in scopt when it's not convenient to reuse parameters. Thanks for reply!
  • Bruno Bieth
    Bruno Bieth over 6 years
    Most validations are performed during the deserialization of the command line parameters (see Read), so if you can define your validation constraints through types (i.e Password, Hex, ...), then you can leverage this.
  • DBear
    DBear about 2 years
    I like this solution since it doesn't require using any external libraries and it is still idiomatic and concise. To address the issue @MauroLacy brought up, I might define an inner singleton object to hold the arguments with their types instead of using a Map.