Лучший способ проанализировать параметры командной строки? [закрытый]
каков наилучший способ анализа параметров командной строки в Scala? Я лично предпочитаю что-то легкое, что не требует внешней банки.
по теме:
26 ответов:
в большинстве случаев вам не нужен внешний парсер. Сопоставления с шаблоном в 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)
эта версия занимает только один файл. Легко улучшить (с помощью списка).
обратите внимание также, что этот подход позволяет объединить несколько аргументов командной строки - даже более двух!
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 }
выше генерирует следующий текст использование:
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
это то, что я в настоящее время использую. Чистое использование без слишком много багажа. (Отказ от ответственности: теперь я поддерживаю этот проект)
Я понимаю, что этот вопрос был задан некоторое время назад, но я подумал, что это может помочь некоторым людям, которые гуглят (как и я), и попали на эту страницу.
гребешок выглядит довольно многообещающе.
особенности (цитата из связанной страницы github):
- флаг, одно значение и множество значений функции
- POSIX-style короткие имена опций (- a) с группировкой (-abc)
- GNU-стиль длинный вариант имена (--опт)
- аргументы свойства (- 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)
Мне нравится скольжения над аргументами для относительно простых конфигураций.
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 }
это во многом бесстыдный клон мой ответ на вопрос Java той же темы. Оказывается, что JewelCLI является Scala-дружественным в том, что он не требует методов стиля JavaBean для получения автоматического именования аргументов.
JewelCLI - это Scala-дружественная библиотека Java для синтаксического анализа командной строки, которая дает чистый код. Он использует Проксированные интерфейсы, настроенные с аннотациями, чтобы динамически создавать типобезопасный API для вашей командной строки параметры.
пример параметр interface
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 в этот каталог, а также.
скомпилируйте и запустите пример в Bash на Linux / Mac OS X / etc.:
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
интерфейс командной строки Scala Toolkit (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) }
вы можете сделать намного больше, конечно (мульти-команды, многие параметры конфигурации,...) и не имеет зависимость.
я закончу с какой-то отличительной чертой, использование по умолчанию (довольно часто пренебрегают для нескольких команд):
Я из мира 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.
там же 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) } } }
scala-optparse-applicative
Я думаю, что scala-optparse-applicative-это самая функциональная библиотека парсера командной строки в Scala.
Мне понравился подход slide() joslinm просто не изменяемый 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 } }
Я только что нашел обширную библиотеку синтаксического анализа командной строки в scala scalac.инструменты.пакет УМК.
Я попытался обобщить решение @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) }
Я основал свой подход на верхнем ответе (от 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)
другое библиотека: scarg
здесь парсер командной строки scala это простой в использовании. Он автоматически форматирует текст справки и преобразует аргументы коммутатора в нужный тип. Поддерживаются как короткие POSIX, так и длинные переключатели стиля GNU. Поддерживает переключатели с обязательными аргументами, необязательными аргументами и несколькими аргументами значений. Вы даже можете указать конечный список допустимых значений для конкретного коммутатора. Длинные названия могут быть сокращены в командной строке для удобства. Подобный параметр parser в стандартной библиотеке Ruby.
Я только что создал мое простое перечисление
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 и устраняете ее во второй раз, набранную в виде текста командной строки).
Я бы предложил использовать http://docopt.org/. есть scala-порт, но реализация Javahttps://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
как анализировать параметры без внешней зависимости. Отличный вопрос! Вы можете быть заинтересованы в picocli.
Picocli специально разработан для решения проблемы, заданной в вопросе: это структура синтаксического анализа командной строки в одном файле, поэтому вы можете включить его в исходном виде. Это позволяет пользователям запускать приложения на основе picocli не требуя пикокли в качестве внешней зависимости.
Он работает путем аннотирования поля так что вы пишете очень мало кода. Краткое резюме:
- строго типизированные все-параметры командной строки, а также позиционные параметры
- поддержка кластеризованных коротких опций POSIX (поэтому он обрабатывает
<command> -xvfInputFile
а также<command> -x -v -f InputFile
)- модель arity, которая позволяет минимальное, максимальное и переменное число параметров, например,
"1..*"
,"3..5"
- беглый и компактный API для минимизации шаблонного клиента код
- команды
- справка по использованию с цветами ANSI
сообщение справки по использованию легко настроить с помощью аннотаций (без программирования). Например:
(источник)
Я не мог удержаться от добавления еще одного скриншота, чтобы показать, какие сообщения справки об использовании возможны. Справка по использованию-это лицо вашего приложения, поэтому будьте изобретательны и имейте весело!
отказ от ответственности: я создал picocli. Обратная связь или вопросы очень приветствуются. Он написан на java, но дайте мне знать, если есть какие-либо проблемы с его использованием в scala, и я попытаюсь решить эту проблему.
Мне нравится чистый вид этого кода... почерпнуть из обсуждения здесь: 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) } }
мне никогда не нравился Рубин, как вариант парсеров. Большинство разработчиков, которые использовали их не правильно на странице для их скриптов и в конечном итоге с длинными страницами параметры не организованы должным образом из-за их парсера.
Я всегда предпочитал способ 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.
Как все разместили это собственное решение здесь мое, потому что я хотел что-то проще написать для пользователя: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) } }
нет причудливых вариантов, чтобы заставить переменную быть в некоторых границах, потому что я не чувствую, что парсер-лучшее место для этого.
Примечание : Вы можете иметь столько псевдонимов, как вы хотите для данной переменной.
Я собираюсь навалить. Я решил это с помощью простой строки кода. Мои аргументы командной строки выглядят так:
input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5
это создает массив через собственную функциональность командной строки Scala (из приложения или основного метода):
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-вкладыш
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)
не нужно ничего импортировать.
Это то, что я приготовила. Он возвращает кортеж из карты и список. Список предназначен для ввода, как и имена входных файлов. Карта предназначена для переключателей / опций.
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)
переключатели могут быть "--Т", X будет присвоено значение true, или "х 10" х, который будет установлен на "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) } } }
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 }
это приведет к следующему использованию: