Cách tốt nhất để phân tích các tham số dòng lệnh? [đóng cửa]


237

Cách tốt nhất để phân tích các tham số dòng lệnh trong Scala là gì? Cá nhân tôi thích một cái gì đó nhẹ mà không cần bình bên ngoài.

Liên quan:

Câu trả lời:


228

Đối với hầu hết các trường hợp, bạn không cần một trình phân tích cú pháp bên ngoài. Kết hợp mẫu của Scala cho phép tiêu thụ args theo kiểu chức năng. Ví dụ:

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

sẽ in, ví dụ:

Map('infile -> test/data/paml-aln1.phy, 'maxsize -> 4, 'minsize -> 2)

Phiên bản này chỉ mất một lần. Dễ dàng cải thiện (bằng cách sử dụng Danh sách).

Cũng lưu ý rằng phương pháp này cho phép ghép nhiều đối số dòng lệnh - thậm chí nhiều hơn hai!


4
isSwitch chỉ cần kiểm tra ký tự đầu tiên là dấu gạch ngang '-'
pjotrp

6
nextOptionkhông phải là một tên tốt cho chức năng. Đây là một hàm trả về bản đồ - thực tế là nó đệ quy là một chi tiết triển khai. Nó giống như viết một maxhàm cho một bộ sưu tập và gọi nó nextMaxđơn giản là vì bạn đã viết nó với đệ quy rõ ràng. Tại sao không chỉ gọi nó optionMap?
itbruce

4
@itsbruce Tôi chỉ muốn thêm vào / sửa đổi quan điểm của bạn - nó sẽ là "phù hợp" nhất từ ​​khả năng đọc / bảo trì để xác định listToOptionMap(lst:List[String])với chức năng nextOptionđược xác định trong đó, với dòng cuối cùng return nextOption(Map(), lst). Điều đó nói rằng, tôi phải thú nhận rằng tôi đã tạo ra nhiều phím tắt lớn hơn trong thời gian của mình so với câu trả lời trong câu trả lời này.
tresbot

6
@theMadKing trong đoạn mã trên exit(1)có thể cần phải cósys.exit(1)
tresbot

3
Tôi thích giải pháp của bạn. Đây là sửa đổi để xử lý nhiều filetham số : 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). Bản đồ cũng cần một giá trị mặc định là Nil, tức là val options = nextOption(Map() withDefaultValue Nil, args.toList). Những gì tôi không thích là phải dùng đến asInstanceOf, do các OptionMapgiá trị thuộc loại Any. Có một giải pháp tốt hơn?
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
}

Ở trên tạo ra văn bản sử dụng sau đây:

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

Đây là những gì tôi hiện đang sử dụng. Sử dụng sạch mà không có quá nhiều hành lý. (Tuyên bố miễn trừ trách nhiệm: Bây giờ tôi duy trì dự án này)


6
Tôi thích mẫu xây dựng DSL tốt hơn nhiều, vì nó cho phép ủy quyền xây dựng tham số cho các mô-đun.
Daniel C. Sobral

3
Lưu ý: không giống như hiển thị, scopt không cần nhiều chú thích kiểu.
Blaisorblade

9
Nếu bạn đang sử dụng điều này để phân tích cú pháp lập luận cho một công việc tia lửa, hãy cảnh báo rằng họ không chơi tốt với nhau. Theo nghĩa đen, không có gì tôi đã cố gắng có thể khiến Spark-submit hoạt động với scopt :-(
jbrown 6/03/2015

4
@BirdJaguarIV Nếu tia lửa sử dụng scopt có lẽ là vấn đề - các phiên bản xung đột trong bình hoặc thứ gì đó. Tôi sử dụng sò với các công việc tia lửa thay thế và không có vấn đề gì.
jbrown

12
Trớ trêu thay mặc dù thư viện này tự động tạo tài liệu CLI tốt, mã trông có vẻ tốt hơn so với brainf * ck.
Jonathan Neufeld

57

Tôi nhận ra rằng câu hỏi đã được hỏi cách đây một thời gian, nhưng tôi nghĩ rằng nó có thể giúp một số người, những người đang đi vòng quanh (như tôi), và nhấn trang này.

Sò điệp trông cũng khá hứa hẹn.

Các tính năng (trích dẫn từ trang github được liên kết):

  • tùy chọn cờ, giá trị đơn và nhiều giá trị
  • Tên tùy chọn ngắn kiểu POSIX (-a) với nhóm (-abc)
  • Tên tùy chọn dài kiểu GNU (--opt)
  • Đối số thuộc tính (-Dkey = value, -D key1 = value key2 = value)
  • Các loại tùy chọn và giá trị thuộc tính không phải chuỗi (với bộ chuyển đổi có thể mở rộng)
  • Kết hợp mạnh mẽ trên trailing args
  • Tiểu ban

Và một số mã ví dụ (cũng từ trang 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
Sò điệp bỏ qua phần còn lại về các tính năng. Xấu hổ về xu hướng SO thông thường của "chiến thắng câu trả lời đầu tiên" đã đẩy điều này xuống danh sách :(
samthebest

Tôi đồng ý. Để lại một bình luận ở đây chỉ cần bỏ qua @Eugene Yokota để ghi chú. Kiểm tra blog này ra sò điệp
Pramit

1
Vấn đề mà nó gây ra với scopt là "Có vẻ tốt, nhưng không thể phân tích các tùy chọn, trong đó có một danh sách các đối số (ví dụ -a 1 2 3). Và bạn không có cách nào mở rộng nó để có được các danh sách đó (ngoại trừ việc bỏ qua lib). " nhưng điều này không còn đúng nữa, xem github.com/scopt/scopt#options .
Alexey Romanov

2
Điều này là trực quan hơn và ít nồi hơi hơn so với scopt. không còn nữa(x, c) => c.copy(xyz = x) trong scopt
WeiChing 煒

43

Tôi thích trượt qua các đối số cho các cấu hình tương đối đơn giản.

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
Tài giỏi. Chỉ hoạt động nếu mọi đối số cũng chỉ định một giá trị, mặc dù, phải không?
Brent Faust

2
Có nên không args.sliding(2, 2)?
m01

1
Có nên không var port = 0?
swdev

17

Bộ công cụ Scala Giao diện dòng lệnh (CLIST)

đây cũng là của tôi (một chút muộn trong trò chơi)

https://github.com/backuity/clist

Trái ngược với scoptnó là hoàn toàn có thể thay đổi ... nhưng chờ đã! Điều đó cho chúng ta một cú pháp khá hay:

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

Và một cách đơn giản để chạy nó:

Cli.parse(args).withCommand(new Cat) { case cat =>
    println(cat.files)
}

Tất nhiên bạn có thể làm nhiều hơn nữa (nhiều lệnh, nhiều tùy chọn cấu hình, ...) và không phụ thuộc.

Tôi sẽ kết thúc với một loại tính năng đặc biệt, cách sử dụng mặc định (thường bị bỏ qua cho nhiều lệnh): phân thân


Liệu nó có xác nhận?
KF

Đúng vậy (xem github.com/backuity/clist/blob/master/demo/src/main/scala/ trộm để biết ví dụ). Nó không được ghi nhận mặc dù ... PR? :)
Bruno Bieth

Đã thử nó, khá thuận tiện. Tôi đã sử dụng scopt trước đây, tôi vẫn chưa quen với việc thêm các xác nhận hợp lệ, nhưng không chỉ trong định nghĩa của từng tham số. Nhưng nó hoạt động tốt với tôi. Và xác định các tham số và xác nhận khác nhau trong các đặc điểm khác nhau, sau đó kết hợp chúng trong các trường hợp khác nhau, điều đó thực sự hữu ích. Tôi đã chịu đựng rất nhiều trong scopt khi nó không thuận tiện để sử dụng lại các tham số. Cảm ơn đã trả lời!
KF

Hầu hết kiểm chứng thực được thực hiện trong thời gian deserialization các tham số dòng lệnh (xem đọc ), vì vậy nếu bạn có thể xác định ràng buộc xác nhận của bạn thông qua các loại (ví dụ Password, Hex...), sau đó bạn có thể tận này.
Bruno Bieth

13

Đây phần lớn là một bản sao không biết xấu hổ trong câu trả lời của tôi cho câu hỏi Java cùng chủ đề . Hóa ra, JewelCLI thân thiện với Scala ở chỗ nó không yêu cầu các phương thức kiểu JavaBean để đặt tên đối số tự động.

JewelCLI là một thư viện Java thân thiện với Scala để phân tích cú pháp dòng lệnh mang lại mã sạch . Nó sử dụng Giao diện được ủy quyền được cấu hình với chú thích để tự động xây dựng API an toàn loại cho các tham số dòng lệnh của bạn.

Một giao diện tham số ví dụ Person.scala:

import uk.co.flamingpenguin.jewel.cli.Option

trait Person {
  @Option def name: String
  @Option def times: Int
}

Một ví dụ sử dụng giao diện tham số 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)
    }
  }
}

Lưu các bản sao của các tệp ở trên vào một thư mục và tải xuống JAR JewelCLI 0.6 vào thư mục đó.

Biên dịch và chạy ví dụ trong Bash trên 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

Biên dịch và chạy ví dụ trong Dấu nhắc lệnh của Windows:

scalac -cp jewelcli-0.6.jar;. Person.scala Hello.scala
scala -cp jewelcli-0.6.jar;. Hello --name="John Doe" --times=3

Chạy ví dụ sẽ mang lại đầu ra sau:

Hello John Doe
Hello John Doe
Hello John Doe

Một điều thú vị trong điều này bạn có thể nhận thấy là (args: _ *). Gọi các phương thức varargs Java từ Scala yêu cầu điều này. Đây là một giải pháp tôi học được từ Daily-scala.blogspot.com/2009/11/varargs.html trên blog Daily Scala tuyệt vời của Jesse Eichar. Tôi đánh giá cao Daily Scala :)
Alain O'Dea

12

Làm thế nào để phân tích các tham số mà không cần một phụ thuộc bên ngoài. Câu hỏi tuyệt vời! Bạn có thể quan tâm đến picocli .

Picocli được thiết kế đặc biệt để giải quyết vấn đề được hỏi trong câu hỏi: đó là khung phân tích cú pháp dòng lệnh trong một tệp duy nhất, vì vậy bạn có thể đưa nó vào dạng nguồn . Điều này cho phép người dùng chạy các ứng dụng dựa trên picocli mà không yêu cầu picocli như một phụ thuộc bên ngoài .

Nó hoạt động bằng cách chú thích các trường để bạn viết rất ít mã. Tóm tắt nhanh:

  • Gõ mạnh mọi thứ - tùy chọn dòng lệnh cũng như các tham số vị trí
  • Hỗ trợ cho các tùy chọn ngắn cụm POSIX (để xử lý <command> -xvfInputFilecũng như <command> -x -v -f InputFile)
  • Một mô hình arity cho phép số lượng tham số tối thiểu, tối đa và thay đổi, ví dụ "1..*","3..5"
  • API thông thạo và nhỏ gọn để giảm thiểu mã máy khách soạn sẵn
  • Tiểu ban
  • Trợ giúp sử dụng với màu ANSI

Thông báo trợ giúp sử dụng dễ dàng tùy chỉnh với các chú thích (không cần lập trình). Ví dụ:

Thông báo trợ giúp sử dụng mở rộng( nguồn )

Tôi không thể cưỡng lại việc thêm một ảnh chụp màn hình để hiển thị loại thông báo trợ giúp sử dụng nào có thể. Trợ giúp sử dụng là bộ mặt của ứng dụng của bạn, vì vậy hãy sáng tạo và vui chơi!

demo picocli

Tuyên bố từ chối trách nhiệm: Tôi đã tạo ra picocli. Phản hồi hoặc câu hỏi rất hoan nghênh. Nó được viết bằng java, nhưng hãy cho tôi biết nếu có bất kỳ vấn đề nào khi sử dụng nó trong scala và tôi sẽ cố gắng giải quyết nó.


1
Tại sao các downvote? Đây là thư viện duy nhất tôi biết về nó được thiết kế đặc biệt để giải quyết vấn đề được đề cập trong OP: làm thế nào để tránh thêm phụ thuộc.
Remko Popma

"khuyến khích các tác giả ứng dụng bao gồm nó". Làm tốt lắm.
keos

bạn có ví dụ scala không?
CruncherBigData

1
Tôi đã bắt đầu tạo các ví dụ cho các ngôn ngữ JVM khác: github.com/remkop/picocli/issues/183 Phản hồi và đóng góp chào mừng!
Remko Popma

11

Tôi đến từ thế giới Java, tôi thích args4j vì đơn giản, đặc điểm kỹ thuật của nó dễ đọc hơn (nhờ các chú thích) và tạo ra đầu ra được định dạng độc đáo.

Đây là đoạn ví dụ của tôi:

Sự chỉ rõ

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]"

}

Phân tích

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

Đối số không hợp lệ

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

Ngoài ra còn có JCommander (từ chối trách nhiệm: Tôi đã tạo ra nó):

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
tôi thích cái này. những trình phân tích cú pháp 'thuần scala' thiếu một cú pháp rõ ràng
chiến thuật

@tactoth kiểm tra cái này, nó có một cú pháp rõ ràng: stackoverflow.com/questions/2315912/iêu
Bruno Bieth

6

Tôi thích cách tiếp cận slide () của joslinm chứ không phải là các vars có thể thay đổi;) Vì vậy, đây là một cách bất biến cho cách tiếp cận đó:

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

Tôi đã cố gắng khái quát hóa giải pháp của @ pjotrp bằng cách đưa vào danh sách các ký hiệu khóa vị trí bắt buộc, bản đồ cờ -> ký hiệu khóa và các tùy chọn mặc định:

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

Tôi đã cập nhật đoạn mã này để xử lý các cờ (không chỉ các tùy chọn có giá trị) và cũng để bắt buộc xác định tùy chọn / cờ với các biểu mẫu ngắn và dài. ví dụ -f|--flags. Hãy xem gist.github.com/DavidGamba/b3287d40b019e498982c và thoải mái cập nhật câu trả lời nếu bạn thích nó. Tôi có thể sẽ thực hiện mọi Bản đồ và tùy chọn để bạn chỉ có thể vượt qua những gì bạn cần với các đối số được đặt tên.
DavidG

3

Tôi dựa trên cách tiếp cận của mình dựa trên câu trả lời hàng đầu (từ dave4420), và cố gắng cải thiện nó bằng cách làm cho nó có mục đích chung hơn.

Nó trả về một Map[String,String]trong tất cả các tham số dòng lệnh Bạn có thể truy vấn điều này cho các tham số cụ thể mà bạn muốn (ví dụ: sử dụng .contains) hoặc chuyển đổi các giá trị thành các loại bạn muốn (ví dụ: sử dụng 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())
}

Thí dụ:

val args=Array("--testing1","testing1","-a","-b","--c","d","test2")
argsToOptionMap( args  )

Cung cấp:

res0: Map[String,String] = Map(testing1 -> testing1, a -> null, b -> null, c -> d, test2 -> null)


2

Đây là một trình phân tích cú pháp dòng lệnh scala dễ sử dụng. Nó tự động định dạng văn bản trợ giúp và nó chuyển đổi các đối số chuyển sang loại mong muốn của bạn. Cả POSIX ngắn và các công tắc kiểu GNU dài đều được hỗ trợ. Hỗ trợ chuyển đổi với các đối số cần thiết, đối số tùy chọn và nhiều đối số giá trị. Bạn thậm chí có thể chỉ định danh sách hữu hạn các giá trị được chấp nhận cho một công tắc cụ thể. Tên chuyển đổi dài có thể được viết tắt trên dòng lệnh cho thuận tiện. Tương tự như trình phân tích cú pháp tùy chọn trong thư viện chuẩn Ruby.


2

Tôi chưa bao giờ thích ruby ​​như trình phân tích cú pháp tùy chọn. Hầu hết các nhà phát triển đã sử dụng chúng không bao giờ viết một trang man phù hợp cho tập lệnh của họ và kết thúc với các trang dài tùy chọn không được tổ chức theo cách thích hợp vì trình phân tích cú pháp của họ.

Tôi luôn thích cách làm việc của Perl với Getopt :: Long của Perl .

Tôi đang làm việc trên một thực hiện scala của nó. API ban đầu trông giống như thế này:

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

Vì vậy, gọi scriptnhư thế này:

$ script hello -f --string=mystring -i 7 --float 3.14 --p --version world -- --nothing

Sẽ in:

higher order function
version is 0.2

Và quay lại:

remaining = Array("hello", "world", "--nothing")

options = Map('flag   -> true,
              'string -> "mystring",
              'int    -> 7,
              'double -> 3.14)

Dự án được lưu trữ trong github scala-getoptions .


2

Tôi chỉ tạo ra liệt kê đơn giản của tôi

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

Tôi hiểu rằng giải pháp đó có hai lỗ hổng lớn có thể khiến bạn mất tập trung: Nó loại bỏ sự tự do (tức là sự phụ thuộc vào các thư viện khác, mà bạn đánh giá rất cao) và dự phòng (nguyên tắc DRY, bạn chỉ gõ tên tùy chọn một lần, như chương trình Scala biến và loại bỏ nó lần thứ hai được gõ dưới dạng văn bản dòng lệnh).


2

Tôi khuyên bạn nên sử dụng http://docopt.org/ . Có một cổng scala nhưng việc triển khai Java https://github.com/docopt/docopt.java hoạt động tốt và dường như được duy trì tốt hơn. Đây là một ví dụ:

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

Đây là những gì tôi nấu. Nó trả về một tuple của bản đồ và một danh sách. Danh sách là cho đầu vào, như tên tập tin đầu vào. Bản đồ là dành cho thiết bị chuyển mạch / tùy chọn.

val args = "--sw1 1 input_1 --sw2 --sw3 2 input_2 --sw4".split(" ")
val (options, inputs) = OptParser.parse(args)

sẽ trở lại

options: Map[Symbol,Any] = Map('sw1 -> 1, 'sw2 -> true, 'sw3 -> 2, 'sw4 -> true)
inputs: List[Symbol] = List('input_1, 'input_2)

Các công tắc có thể là "--t" mà x sẽ được đặt thành true hoặc "--x 10" mà x sẽ được đặt thành "10". Mọi thứ khác sẽ kết thúc trong danh sách.

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

Tôi thích giao diện rõ ràng của mã này ... lượm lặt từ một cuộc thảo luận ở đây: 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

Như mọi người đã đăng giải pháp riêng của mình ở đây là của tôi, vì tôi muốn viết một cái gì đó dễ dàng hơn cho người dùng: https://gist.github.com/gwenzek/78355526e476e08bb34d

Các ý chính chứa một tệp mã, cộng với một tệp thử nghiệm và một ví dụ ngắn được sao chép ở đây:

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

Không có tùy chọn ưa thích nào để buộc một biến nằm trong một số giới hạn, vì tôi không cảm thấy rằng trình phân tích cú pháp là nơi tốt nhất để làm như vậy.

Lưu ý: bạn có thể có nhiều bí danh như bạn muốn cho một biến đã cho.


1

Tôi sẽ chồng chất lên. Tôi đã giải quyết điều này với một dòng mã đơn giản. Đối số dòng lệnh của tôi trông như thế này:

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

Điều này tạo ra một mảng thông qua chức năng dòng lệnh gốc của Scala (từ Ứng dụng hoặc phương thức chính):

Array("input--hdfs:/path/to/myData/part-00199.avro", "output--hdfs:/path/toWrite/Data","fileFormat--avro","option1--5")

Sau đó tôi có thể sử dụng dòng này để phân tích mảng args mặc định:

val nArgs = args.map(x=>x.split("--")).map(y=>(y(0),y(1))).toMap

Tạo bản đồ với các tên được liên kết với các giá trị dòng lệnh:

Map(input -> hdfs:/path/to/myData/part-00199.avro, output -> hdfs:/path/toWrite/Data, fileFormat -> avro, option1 -> 5)

Sau đó tôi có thể truy cập các giá trị của các tham số được đặt tên trong mã của mình và thứ tự chúng xuất hiện trên dòng lệnh không còn phù hợp. Tôi nhận ra điều này khá đơn giản và không có tất cả các chức năng nâng cao được đề cập ở trên nhưng dường như là đủ trong hầu hết các trường hợp, chỉ cần một dòng mã và không liên quan đến các phụ thuộc bên ngoài.


1

Đây là của tôi 1-lót

    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)

Nó giảm 3 đối số bắt buộc và đưa ra các tùy chọn. Các số nguyên được chỉ định như -Xmx<size>tùy chọn java khét tiếng , cùng với tiền tố. Bạn có thể phân tích nhị phân và số nguyên đơn giản như

val cacheEnabled = optSpecified("cacheOff")
val memSize = optInt("-Xmx", 1000)

Không cần nhập gì cả.


0

Một lớp lót nhanh và bẩn của người nghèo để phân tích khóa = cặp giá trị:

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

tự do

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
}

Điều này sẽ tạo ra cách sử dụng sau:

Sử dụng

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.