Best way to parse command-line parameters?
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):
Related videos on Youtube
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, 2020Comments
-
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 over 14 yearsI like the builder pattern DSL much better, because it enables delegation of parameter construction to modules.
-
ThanksForYourHelp almost 14 yearsOne 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 almost 12 yearsNote: unlike shown, scopt doesn't need that many type annotations.
-
MCP almost 11 yearsVery 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 almost 11 yearsisSwitch simply checks for the first character being a dash '-'
-
DavidGamba about 10 yearsI 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 almost 10 yearsscopt 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 over 9 yearsdoes it have any examples/doc in addition to what's in the README?
-
Rollen Holt over 9 yearsdoes scopt project has some document?
-
appbootup over 9 yearsJust wanted to point out that Argot is definitely on Maven Central (have been using it for a few years now).
-
gpampara over 9 yearsIt does, check the
examples
in the test code -
jbrown over 9 yearsIf 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 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 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 about 9 yearsIronically though this library automagically generates good CLI documentation, the code looks little better than brainf*ck.
-
theMadKing about 9 yearsI am getting a: cannot resolve symbol exit. Can anyone help out as to why this might be happening?
-
Brent Faust about 9 yearsClever. Only works if every arg also specifies a value, though, right?
-
tactoth almost 9 yearsi like this one. those 'pure scala' parsers lack a clean syntax
-
samthebest almost 9 yearsScallop 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 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 amax
function for a collection and calling itnextMax
simply because you wrote it with explicit recursion. Why not just call itoptionMap
? -
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 functionnextOption
defined within that, with a final line sayingreturn 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 almost 9 years@theMadKing in the code above
exit(1)
may need to besys.exit(1)
-
Pramit over 8 yearsI agree. Leaving a comment here just incase @Eugene Yokota missed to take a note. Check this blog out scallop
-
Bruno Bieth over 8 years@tactoth check this one, it has a clear syntax: stackoverflow.com/questions/2315912/…
-
Andriy Drozdyuk over 8 yearsWhat are the import statements? My IDE can't find "Config".
-
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 about 8 yearsThe 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 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 over 7 yearsI 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 ofNil
, i.e.val options = nextOption(Map() withDefaultValue Nil, args.toList)
. What I don't like is having to resort toasInstanceOf
, due to theOptionMap
values being of typeAny
. Is there a better solution? -
Jorge over 7 yearsHere 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 over 7 yearsWhen 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 over 7 yearsShould it not be
args.sliding(2, 2)
? -
acumartini over 7 yearsI prefer scallop (github.com/scallop/scallop). Much simpler implementation, cleaner docs. No offense to the maintainers of scopt, I just prefer KISS.
-
WeiChing 林煒清 over 7 yearsthis is more intuitive and less boilerplate than scopt. no more
(x, c) => c.copy(xyz = x)
in scopt -
Antonin over 7 yearsI 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 about 7 yearsWhy 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 about 7 yearsShould it not be
var port = 0
? -
cuz almost 7 years"encourage application authors to include it". Good work.
-
CruncherBigData almost 7 yearsdo you have scala examples?
-
Remko Popma almost 7 yearsI've started to create examples for other JVM languages: github.com/remkop/picocli/issues/183 Feedback and contributions welcome!
-
K F over 6 yearsDoes it have validation?
-
Bruno Bieth over 6 yearsYes it does (see github.com/backuity/clist/blob/master/demo/src/main/scala/… for an example). It's not documented though... PR? :)
-
K F over 6 yearsTried 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 over 6 yearsMost 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 about 2 yearsI 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.