256

Scalaでコマンドラインパラメータを解析する最良の方法は何ですか? 個人的には、外部 jar を必要としない軽量なものを好みます。

関連している:

4

26 に答える 26

239

ほとんどの場合、外部パーサーは必要ありません。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)

このバージョンは、1 つの infile のみを使用します。(リストを使用して)簡単に改善できます。

このアプローチでは、複数のコマンド ライン引数を連結できることにも注意してください。

于 2010-07-06T06:29:38.053 に答える
202

スコット/スコット

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

これは私が現在使用しているものです。荷物を持ちすぎずにすっきりと使えます。(免責事項:私は現在、このプロジェクトを維持しています)

于 2010-02-23T03:29:30.317 に答える
62

この質問は少し前に出されたものだと思いますが、(私のように) グーグルで検索していて、このページにたどり着いた人の助けになるかもしれないと思いました。

ホタテも非常に有望に見えます。

機能 (リンクされた github ページからの引用):

  • フラグ、単一値および複数値オプション
  • POSIX スタイルの短いオプション名 (-a) とグループ化 (-abc)
  • GNU スタイルの長いオプション名 (--opt)
  • プロパティ引数 (-Dkey=値、-D key1=値 key2=値)
  • 非文字列型のオプションとプロパティ値 (拡張可能なコンバーターを使用)
  • 末尾の引数に対する強力なマッチング
  • サブコマンド

そして、いくつかのサンプルコード (これも 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)
于 2012-11-11T23:18:35.107 に答える
14

外部依存関係なしでパラメーターを解析する方法。素晴らしい質問です。picocliに興味があるかもしれません。

Picocli は、質問で尋ねられた問題を解決するために特別に設計されています。これは、単一のファイル内のコマンド ライン解析フレームワークであるため、ソース フォームに含めることができます。これにより、ユーザーは picocli を外部依存関係として必要とせずに picocliベースのアプリケーションを実行できます。

フィールドに注釈を付けることで機能するため、コードをほとんど記述しません。簡単な要約:

  • 厳密に型指定されたすべて - コマンド ライン オプションと位置パラメータ
  • POSIX クラスター化された短いオプションのサポート (したがって、<command> -xvfInputFileと同様に処理されます<command> -x -v -f InputFile)
  • パラメーターの最小数、最大数、および可変数を許可するアリティ モデル"1..*""3..5"
  • ボイラープレート クライアント コードを最小限に抑える流暢でコンパクトな API
  • サブコマンド
  • ANSI カラーの使い方のヘルプ

使い方のヘルプ メッセージは、注釈を使用して簡単にカスタマイズできます (プログラミングなしで)。例えば:

拡張使用ヘルプ メッセージソース

どのような使用方法のヘルプ メッセージが可能なのかを示すために、スクリーンショットをもう 1 つ追加せずにはいられませんでした。使い方のヘルプはアプリケーションの顔なので、創造性を発揮して楽しんでください!

picocli デモ

免責事項: picocli を作成しました。フィードバックや質問は大歓迎です。これは Java で書かれていますが、scala での使用に問題がある場合はお知らせください。対処を試みます。

于 2017-05-04T13:37:14.860 に答える
13

これは主に、同じトピックの Java の質問に対する私の回答の恥知らずな複製です。自動引数命名を取得するために JavaBean スタイルのメソッドを必要としないという点で、JewelCLI は Scala に適していることがわかります。

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

上記のファイルのコピーを 1 つのディレクトリに保存し、JewelCLI 0.6 JARもそのディレクトリにダウンロードします。

Linux/Mac OS X/etc の 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
于 2010-07-26T23:41:24.817 に答える
12

私は 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.
于 2016-02-02T00:07:26.120 に答える
11

scala-optparse-applicative

scala-optparse-applicative は、Scala で最も機能的なコマンド ライン パーサー ライブラリだと思います。

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

于 2014-09-30T15:57:21.670 に答える
9

私は変更可能な vars ではなく、joslinm の slide() アプローチが好きでした ;) したがって、そのアプローチに対する不変の方法を次に示します。

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
  }
}
于 2016-11-06T14:45:31.610 に答える
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)
    }
  }
}
于 2012-03-16T17:33:15.093 に答える
3

私はトップの回答(dave4420から)に基づいてアプローチし、より汎用的にすることで改善を試みました。

これはMap[String,String]すべてのコマンド ラインパラメータ.containstoInt

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)
于 2016-08-02T08:59:14.980 に答える
3

私はルビーのようなオプションパーサーが好きではありませんでした。それらを使用したほとんどの開発者は、スクリプトの適切なman ページを作成せず、パーサーのために適切な方法で編成されていないページの長いオプションを作成することになります。

私はいつも、Perl のGetopt::Longを使った Perl のやり方を好んでいました。

私はそれの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でホストされています。

于 2014-06-01T22:27:14.313 に答える
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)
}
于 2013-09-27T17:24:26.797 に答える
3

scalac の scala.tools.cmd パッケージで、広範なコマンド ライン解析ライブラリを見つけました。

http://www.assembla.com/code/scala-eclipse-toolchain/git/nodes/src/compiler/scala/tools/cmd?rev=f59940622e32384b1e08939effd24e924a8ba8dbを参照してください。

于 2010-08-26T17:17:12.820 に答える
3

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
于 2015-11-11T10:06:06.443 に答える
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

ソリューションには、気を散らす可能性のある 2 つの大きな欠陥があることを理解しています: 自由 (つまり、あなたが非常に重視している他のライブラリへの依存) と冗長性 (DRY 原則、Scala プログラムのようにオプション名を 1 回だけ入力する) を排除します。変数を削除し、コマンド ライン テキストとして 2 回目に入力したものを削除します)。

于 2014-07-20T12:53:36.990 に答える
2

別のライブラリ: Scarg

于 2010-11-21T00:14:11.237 に答える
2

これは、使いやすいscala コマンド ライン パーサーです。ヘルプ テキストを自動的にフォーマットし、スイッチ引数を目的の型に変換します。短い POSIX と長い GNU スタイル スイッチの両方がサポートされています。必須の引数、オプションの引数、および複数の値の引数を持つスイッチをサポートします。特定のスイッチの許容値の有限リストを指定することもできます。便宜上、長いスイッチ名はコマンド ラインで省略できます。Ruby 標準ライブラリのオプション パーサーに似ています。

于 2011-01-30T17:53:58.243 に答える
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)
    }
}

パーサーがそうするのに最適な場所であるとは思わないので、変数を何らかの範囲内に強制するための派手なオプションはありません。

注 : 特定の変数に対して必要なだけエイリアスを設定できます。

于 2014-07-16T16:52:37.660 に答える
1

積み上げていきます。簡単なコード行でこれを解決しました。コマンドライン引数は次のようになります。

input--hdfs:/path/to/myData/part-00199.avro output--hdfs:/path/toWrite/Data fileFormat--avro option1--5

これにより、Scala のネイティブ コマンド ライン機能を介して (App または main メソッドから) 配列が作成されます。

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 行のコードで十分であり、外部依存関係を必要としません。

于 2015-09-30T13:47:46.207 に答える
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)

何もインポートする必要はありません。

于 2015-12-07T21:28:18.743 に答える
0

key=value ペアを解析するための貧弱な人の手っ取り早いワンライナー:

def main(args: Array[String]) {
    val cli = args.map(_.split("=") match { case Array(k, v) => k->v } ).toMap
    val saveAs = cli("saveAs")
    println(saveAs)
}
于 2016-06-29T18:20:44.833 に答える