Лучший способ разобрать параметры командной строки? [закрыто]

237

Как лучше всего анализировать параметры командной строки в Scala? Я лично предпочитаю что-то легкое, не требующее внешней банки.

Связанный:

Евгений Йокота
источник

Ответы:

228

В большинстве случаев вам не нужен внешний парсер. Сопоставление с образцом в Scala позволяет использовать аргументы в функциональном стиле. Например:

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)

Эта версия занимает всего один инфиль. Легко улучшить (с помощью списка).

Также обратите внимание, что этот подход позволяет объединять несколько аргументов командной строки - даже более двух!

pjotrp
источник
4
isSwitch просто проверяет, является ли первый символ тире '-'
pjotrp
6
nextOptionне хорошее название для функции. Это функция, которая возвращает карту - тот факт, что она является рекурсивной, является деталью реализации. Это похоже на написание maxфункции для коллекции и вызов ее nextMaxпросто потому, что вы написали ее с явной рекурсией. Почему бы просто не позвонить optionMap?
Брюс
4
@itsbruce Я просто хочу добавить / изменить вашу точку зрения - это было бы наиболее "правильным" из удобочитаемости / удобства сопровождения для определения listToOptionMap(lst:List[String])с помощью функции, nextOptionопределенной в ней, с заключительной строчкой return nextOption(Map(), lst). Тем не менее, я должен признаться, что я сделал гораздо больше вопиющих ярлыков в свое время, чем тот, что в этом ответе.
Tresbot
6
@ theMadKing в приведенном выше коде exit(1)может потребоватьсяsys.exit(1)
tresbot
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. Есть ли лучшее решение?
Мауро Лейси
196

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

Это то, что я сейчас использую. Чистое использование без большого количества багажа. (Отказ от ответственности: я сейчас поддерживаю этот проект)

Евгений Йокота
источник
6
Мне гораздо больше нравится шаблон построителя DSL, поскольку он позволяет делегировать построение параметров модулям.
Даниэль С. Собрал
3
Примечание: в отличие от показанного, scopt не нуждается в таком количестве аннотаций типов.
Blaisorblade
9
Если вы используете это для анализа аргументов для работы с искрой, имейте в виду, что они не очень хорошо играют вместе. Буквально ничего из того, что я пробовал, не могло заставить искру подчиниться работе со Скоптом :-(
jbrown
4
@BirdJaguarIV Если спарк использует scopt, возможно, это была проблема - конфликтующие версии в банке или что-то в этом роде. Вместо этого я использую гребешок с искровыми заданиями, и у меня не было проблем.
августа
12
По иронии судьбы, хотя эта библиотека автоматически генерирует хорошую документацию по CLI, код выглядит немного лучше, чем brainf * ck.
Джонатан Нойфельд
58

Я понимаю, что вопрос был задан некоторое время назад, но я подумал, что это может помочь некоторым людям, которые ищут в Google (например, я), и попасть на эту страницу.

Гребешок выглядит довольно многообещающе.

Особенности (цитата из связанной страницы GitHub):

  • флаг, однозначные и множественные значения
  • Короткие имена опций в стиле POSIX (-a) с группировкой (-abc)
  • Длинные имена опций в стиле GNU (--opt)
  • Аргументы свойства (-Dkey = значение, -D ключ1 = значение ключ2 = значение)
  • Нестроковые типы значений параметров и свойств (с расширяемыми конвертерами)
  • Мощное сопоставление на конечных аргументах
  • Подкоманды

И пример кода (также с этой страницы 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)
rintcius
источник
4
Гребешок остальной руки опускает вниз с точки зрения особенностей. Стыд, как обычно, из-за тенденции «выигрывают первые ответы» вытолкнул это из списка :(
samthebest
Я согласен. Оставив здесь комментарий, просто @Eugene Yokota пропустил записку. Проверьте этот блог из гребешка
Pramit
1
Проблема, стоящая перед scopt, выглядит так: «Выглядит хорошо, но не может разобрать параметры, которые принимают список аргументов (т.е. -a 1 2 3). И у вас нет возможности расширить его, чтобы получить эти списки (кроме разветвления Lib) «. но это больше не так, см. github.com/scopt/scopt#options .
Алексей Романов
2
это более интуитивно понятно и менее шаблонно, чем scopt. больше нет (x, c) => c.copy(xyz = x) в области
WeiChing hing 煒 清
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
}
joslinm
источник
2
Умная. Работает, только если каждый аргумент также указывает значение, верно?
Брент Фауст
2
Не должно ли это быть args.sliding(2, 2)?
m01
1
Не должно ли это быть var port = 0?
swdev
17

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

Конечно, вы можете сделать намного больше (много команд, много опций конфигурации, ...) и не зависеть.

Я закончу с некоторой отличительной особенностью, использованием по умолчанию (довольно часто игнорируемым для нескольких команд): CLIST

Бруно Бит
источник
Есть ли проверка?
KF
Да, это так (см. Пример github.com/backuity/clist/blob/master/demo/src/main/scala/… ). Это не задокументировано, хотя ... PR? :)
Bruno Bieth
Пробовал, довольно удобно. Я использовал scopt раньше, я все еще не привык добавлять валидации, но не только в определении каждого параметра. Но это прекрасно работает со мной. И определение различных параметров и проверок в разных чертах, а затем объединение их в разных случаях - это действительно полезно. Я сильно пострадал, когда не удобно повторно использовать параметры. Спасибо за ответ!
KF
Большинство валидации выполняется во время десериализации параметров командной строки (см Read ), так что если вы можете определить ограничение проверки с помощью типов (то есть Password, Hex...), то вы можете использовать это.
Бруно Бит
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)
    }
  }
}

Сохраните копии файлов выше в одном каталоге и загрузите JAR JewelCLI 0.6 в этот каталог.

Скомпилируйте и запустите пример в 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
Ален О'Ди
источник
Вы можете заметить одну забавную вещь (args: _ *). Вызов методов Java varargs из Scala требует этого. Это решение, которое я узнал из daily-scala.blogspot.com/2009/11/varargs.html в отличном блоге Джесси Эйчар Daily Daily Scala. Я очень рекомендую Daily Scala :)
Ален О'Ди
12

Как разобрать параметры без внешней зависимости. Отличный вопрос! Вы можете быть заинтересованы в пикокли .

Picocli специально разработан для решения проблемы, заданной в вопросе: это среда синтаксического анализа командной строки в одном файле, поэтому вы можете включить ее в исходную форму . Это позволяет пользователям запускать приложения на основе picocli, не требуя picocli в качестве внешней зависимости .

Он работает путем аннотирования полей, поэтому вы пишете очень мало кода. Краткое резюме:

  • Все строго набрано - параметры командной строки, а также позиционные параметры
  • Поддержка кластерных коротких опций POSIX (так что он обрабатывает <command> -xvfInputFileтак же, как <command> -x -v -f InputFile)
  • Модель арности, которая допускает минимальное, максимальное и переменное количество параметров, например "1..*","3..5"
  • Свободный и компактный API для минимизации стандартного кода клиента
  • Подкоманды
  • Помощь по использованию с цветами ANSI

Сообщение об использовании легко настроить с помощью аннотаций (без программирования). Например:

Расширенное сообщение об использовании( источник )

Я не мог удержаться от добавления еще одного скриншота, чтобы показать, какие виды справочных сообщений об использовании возможны. Справка по использованию - это лицо вашего приложения, так что будьте изобретательны и получайте удовольствие!

пикокли демо

Отказ от ответственности: я создал picocli. Отзывы или вопросы очень приветствуются. Он написан на Java, но дайте мне знать, если есть какие-либо проблемы с его использованием в Scala, и я постараюсь решить ее.

Ремко Попма
источник
1
Почему отрицательный голос? Это единственная библиотека, о которой я знаю, которая специально разработана для решения проблемы, упомянутой в ОП: как избежать добавления зависимости.
Ремко
msgstr "рекомендовать авторам приложения включать его". Хорошая работа.
Кеос
у вас есть примеры Scala?
CruncherBigData
1
Я начал создавать примеры для других языков JVM: github.com/remkop/picocli/issues/183 Отзывы и комментарии приветствуются!
Ремко
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.
Тамме Говда
источник
10

Скала-optparse-аппликативны

Я думаю, что scala-optparse-Applicative является самой функциональной библиотекой парсера командной строки в Scala.

https://github.com/bmjames/scala-optparse-applicative

Кендзи Йошида
источник
есть ли какие-либо примеры / документы в дополнение к тому, что в README?
Эрик Каплун
1
Это делает, проверьте examplesв тестовом коде
gpampara
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
мне нравится этот. этим синтаксическим анализаторам "чистого скала" не хватает чистого синтаксиса
такт
@tactoth проверьте это, у него есть четкий синтаксис: stackoverflow.com/questions/2315912/…
Bruno Bieth
6

Мне понравился метод joslinm, использующий слайд (), но не изменяемые переменные;) Так что вот неизменный путь к этому подходу:

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
  }
}
haggy
источник
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 и не стесняйтесь обновлять ответ, если вам это нравится. Я, вероятно, сделаю каждую карту и опцию, чтобы вы могли передавать только то, что вам нужно, с именованными аргументами.
DavidG
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)
bjorno
источник
2

Вот анализатор командной строки Scala, который прост в использовании. Он автоматически форматирует текст справки и преобразует аргументы переключателя в нужный вам тип. Поддерживаются как короткие POSIX, так и длинные переключатели в стиле GNU. Поддерживает переключатели с необходимыми аргументами, необязательными аргументами и аргументами с несколькими значениями. Вы даже можете указать конечный список допустимых значений для конкретного переключателя. Длинные имена переключателей могут быть сокращены в командной строке для удобства. Аналогично параметру parser в стандартной библиотеке Ruby.

sellmerfud
источник
2

Я никогда не любил рубиновые парсеры опций. Большинство разработчиков, которые их использовали, никогда не пишут правильную справочную страницу для своих сценариев и в итоге получают длинные страницы, не организованные должным образом из-за их синтаксического анализатора.

Я всегда предпочитал способ работы Perl с помощью Perl's Getopt :: Long .

Я работаю над его реализацией. Ранний 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 .

DavidG
источник
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). Переменный и устранить его во второй раз набранный как текст командной строки).

Val
источник
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 будет установлено в "истина", или "--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)
    }
  }
}
auselen
источник
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)
    }
}

Там нет причудливых вариантов, чтобы заставить переменную находиться в определенных границах, потому что я не чувствую, что синтаксический анализатор - лучшее место для этого.

Примечание: вы можете иметь столько псевдонимов, сколько хотите для данной переменной.

gwenzek
источник
1

Я собираюсь нагромождать. Я решил это с помощью простой строки кода. Мои аргументы командной строки выглядят так:

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)

Затем я могу получить доступ к значениям именованных параметров в моем коде, и порядок их появления в командной строке больше не имеет значения. Я понимаю, что это довольно просто и не имеет всей расширенной функциональности, упомянутой выше, но кажется достаточной в большинстве случаев, требует только одну строку кода и не включает внешних зависимостей.

J Calbreath
источник
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)
}
botkop
источник
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
}

Это сгенерирует следующее использование:

использование

pavlosgi
источник