명령 줄 매개 변수를 구문 분석하는 가장 좋은 방법은 무엇입니까? [닫은]


237

스칼라에서 명령 줄 매개 변수를 구문 분석하는 가장 좋은 방법은 무엇입니까? 나는 개인적으로 외부 항아리가 필요없는 가벼운 것을 선호합니다.

관련 :

답변:


228

대부분의 경우 외부 파서가 필요하지 않습니다. 스칼라의 패턴 매칭은 기능적인 스타일로 인수를 소비 할 수 있습니다. 예를 들면 다음과 같습니다.

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

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

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이 훨씬 더 좋습니다.
Daniel C. Sobral

3
참고 : 표시된 것과 달리 scopt에는 많은 유형 주석이 필요하지 않습니다.
Blaisorblade

9
스파크 작업을 위해 인수를 구문 분석하는 데 이것을 사용하는 경우 서로 잘 어울리지 않는다는 경고가 표시됩니다. 말 그대로 scopt와 함께 작동하기 위해 spark-submit을 얻을 수있는 것은 없었습니다 :-(
jbrown

4
@BirdJaguarIV spark에서 scopt를 사용하면 아마도 문제가 있었을 것입니다. 대신 스파크 작업에 가리비를 사용하고 아무런 문제가 없었습니다.
jbrown

12
아이러니하게도이 라이브러리는 자동으로 우수한 CLI 문서를 생성하지만 코드는 brainf * ck보다 약간 나아 보입니다.
Jonathan Neufeld 2016 년

58

나는 그 질문이 얼마 전에 제기되었다는 것을 알고 있지만, 나처럼 인터넷을 검색 하고이 페이지를 방문하는 일부 사람들에게 도움이 될 것이라고 생각했습니다.

가리비 매우 유망 해 보입니다.

기능 (링크 된 github 페이지에서 인용) :

  • 플래그, 단일 값 및 다중 값 옵션
  • 그룹화 (-abc)가있는 POSIX 스타일 짧은 옵션 이름 (-a)
  • GNU 스타일의 긴 옵션 이름 (--opt)
  • 속성 인수 (-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)

4
가리비는 특징면에서 나머지 손을 wn니다. "첫 번째 응답 승리"의 일반적인 SO 추세가 수치를
낮추었습니다.

나는 동의한다. @Eugene Yokota가 메모를 놓친 경우를 대비하여 여기에 의견을 남기십시오. 가리비를
Pramit

1
scopt에서 언급하는 문제는 "좋아 보이지만 인수 목록을 취하는 옵션을 구문 분석 할 수 없습니다 (예 : -a 1 2 3).이 목록을 가져 오기 위해 확장 할 수있는 방법이 없습니다. lib). " 그러나 이것은 더 이상 사실이 아닙니다 ( github.com/scopt/scopt#options 참조) .
Alexey Romanov 2016 년

2
이것은 scopt보다 직관적이고 덜 상용 적입니다. 더 이상 (x, c) => c.copy(xyz = x) scopt
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
영리한. 모든 인수가 값을 지정하는 경우에만 작동합니다.
브렌트 파우스트

2
그렇지 않아야 args.sliding(2, 2)합니까?
m01

1
그렇지 않아야 var port = 0합니까?
swdev 2016 년

17

커맨드 라인 인터페이스 스칼라 툴킷 (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, ...)을 통해 유효성 검사 제약 조건을 정의 할 수 있으면 이를 활용할 수 있습니다.
Bruno Bieth

13

이것은 같은 주제의 Java 질문에 대한 나의 답변 의 무모한 복제물입니다 . JewelCLI는 자동 인수 명명을 얻기 위해 JavaBean 스타일 메소드가 필요하지 않다는 점에서 스칼라 친화적이라는 것이 밝혀졌습니다.

JewelCLI는 깨끗한 코드를 생성하는 명령 줄 구문 분석을위한 스칼라 친화적 인 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 등의 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 : _ *)입니다. 스칼라에서 Java varargs 메소드를 호출하려면 이것이 필요합니다. 이것은 Jesse Eichar의 탁월한 Daily Scala 블로그에 대해 daily-scala.blogspot.com/2009/11/varargs.html 에서 배운 솔루션 입니다. 나는 Daily Scala를 강력히 추천한다 :)
Alain O'Dea

12

외부 종속성없이 매개 변수를 구문 분석하는 방법 좋은 질문입니다! 당신은 picocli에 관심이있을 수 있습니다 .

Picocli는 특히 질문에 제기 된 문제를 해결하도록 설계되었습니다. 단일 파일의 명령 줄 구문 분석 프레임 워크이므로 소스 형식으로 포함 할 수 있습니다 . 이를 통해 사용자는 picocli를 외부 종속성으로 요구하지 않고 picocli 기반 응용 프로그램 실행할 수 있습니다 .

필드에 주석을 달아서 코드를 거의 작성하지 않습니다. 빠른 요약 :

  • 명령 줄 옵션 및 위치 매개 변수
  • POSIX 클러스터 짧은 옵션 지원 ( <command> -xvfInputFile뿐만 아니라 처리 <command> -x -v -f InputFile)
  • 파라미터의 최소값, 최대 값 및 가변 수 있도록하는 인수에 대응 모델, 예를 들면 "1..*","3..5"
  • 보일러 플레이트 클라이언트 코드를 최소화하는 유창하고 컴팩트 한 API
  • 부속 명령
  • ANSI 색상 사용 도움말

사용법 도움말 메시지는 프로그래밍없이 주석으로 쉽게 사용자 정의 할 수 있습니다. 예를 들면 다음과 같습니다.

확장 된 사용법 도움말 메시지( 소스 )

스크린 샷을 하나 더 추가하여 어떤 종류의 사용 도움말 메시지가 가능한지 보여줄 수 없었습니다. 사용 도움말은 응용 프로그램의 얼굴이므로 창의적이고 재미있게 보내십시오!

피코 클리 데모

면책 조항 : 나는 picocli를 만들었습니다. 의견이나 질문은 매우 환영합니다. Java로 작성되었지만 스칼라에서 사용하는 데 문제가 있으면 알려 주시면 해결하겠습니다.


1
왜 공감해야합니까? 이것은 내가 알고있는 유일한 라이브러리이며 OP에서 언급 한 문제를 해결하기 위해 특별히 고안되었습니다 : 종속성 추가를 피하는 방법.
Remko Popma

"응용 프로그램 작성자가이를 포함하도록 권장" 잘 했어.
keos

스칼라 예제가 있습니까?
CruncherBigData

1
다른 JVM 언어에 대한 예제를 만들기 시작했습니다. github.com/remkop/picocli/issues/183 피드백 및 기여에 오신 것을 환영합니다!
Remko Popma

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
난이게 좋아. 그 '순수한 스칼라'파서는 깨끗한 문법이 없다
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를 보고 원하는 경우 답변을 자유롭게 업데이트하십시오. 아마도 모든 맵과 옵션을 만들 것이므로 명명 된 인수로 필요한 것만 전달할 수 있습니다.
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)


2

다음 은 사용하기 쉬운 스칼라 명령 줄 파서 입니다. 도움말 텍스트의 서식을 자동으로 지정하고 스위치 인수를 원하는 유형으로 변환합니다. 짧은 POSIX 및 긴 GNU 스타일 스위치가 모두 지원됩니다. 필수 인수, 선택적 인수 및 다중 값 인수가있는 스위치를 지원합니다. 특정 스위치에 대해 허용 가능한 값의 유한 목록을 지정할 수도 있습니다. 편의를 위해 긴 스위치 이름을 명령 줄에서 축약 할 수 있습니다. Ruby 표준 라이브러리의 옵션 파서와 유사합니다.


2

나는 옵션 파서처럼 루비를 좋아하지 않았습니다. 그것들을 사용한 대부분의 개발자는 스크립트에 대한 적절한 매뉴얼 페이지 를 작성하지 않으며 파서 때문에 올바른 방식으로 구성되지 않은 페이지 긴 옵션으로 끝납니다.

나는 Perl의 Getopt :: Long 으로 Perl의 작업 방식을 항상 선호했습니다 .

나는 스칼라 구현을 위해 노력하고 있습니다. 초기 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 원칙, 옵션 이름을 스칼라 프로그램으로 한 번만 입력합니다)을 제거합니다 변수를 입력하고 명령 줄 텍스트로 입력 한 두 번째 시간을 제거하십시오).


2

http://docopt.org/ 를 사용하는 것이 좋습니다 . 스칼라 포트가 있지만 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 또는 기본 방법)을 통해 배열을 만듭니다.

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

키 = 값 쌍을 구문 분석하기위한 불쌍한 사람의 빠르고 더러운 1 라이너 :

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

프리 클리

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
}

다음과 같은 사용법이 생성됩니다.

용법

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.