在Java中如何解析命令行参数?

683

在Java中解析命令行参数的好方法是什么?


17
请注意,CLI库不是线程安全的。然而,我会假设在应用程序启动期间,通常会在单个线程中完成命令行解析,然后根据参数启动其他线程。 - Alexey Ivanov
6
请参考args4j和详细示例以了解如何使用它:http://martin-thoma.com/how-to-parse-command-line-arguments-in-java/ - Martin Thoma
@RedGlyph - 看起来SO/SE需要简化他们的规则。问题应该是:“如何解析Java命令行参数?”但是没有人真正想编写代码来做这件事,而是想使用工具。但是搜索工具等并不是建设性的 :( - AlikElzin-kilaka
14
投了重新开放的一票。@AlikElzin:确实,他们需要审查他们的管理过程。我怀疑关闭问题会获取一个徽章,并且这个徽章会吸引那些想当管理员但过于热衷的人。 - RedGlyph
9
这个问题是用来诱捕坏的/单行回答和工具推荐的,它应该保持关闭状态。 - JAL
显示剩余3条评论
21个回答

513

看看这些:

或自己编写:


例如,这是使用commons-cli解析2个字符串参数的方式:

import org.apache.commons.cli.*;

public class Main {


    public static void main(String[] args) throws Exception {

        Options options = new Options();

        Option input = new Option("i", "input", true, "input file path");
        input.setRequired(true);
        options.addOption(input);

        Option output = new Option("o", "output", true, "output file");
        output.setRequired(true);
        options.addOption(output);

        CommandLineParser parser = new DefaultParser();
        HelpFormatter formatter = new HelpFormatter();
        CommandLine cmd = null;//not a good practice, it serves it purpose 

        try {
            cmd = parser.parse(options, args);
        } catch (ParseException e) {
            System.out.println(e.getMessage());
            formatter.printHelp("utility-name", options);

            System.exit(1);
        }

        String inputFilePath = cmd.getOptionValue("input");
        String outputFilePath = cmd.getOptionValue("output");

        System.out.println(inputFilePath);
        System.out.println(outputFilePath);

    }

}

命令行用法:

$> java -jar target/my-utility.jar -i asd                                                                                       
Missing required option: o

usage: utility-name
 -i,--input <arg>    input file path
 -o,--output <arg>   output file

61
请注意,与许多其他Apache库不同,Apache CLI没有任何依赖关系。 - Vladimir Dyuzhev
11
许多 Apache Commons 项目的一个缺点是它们的提交次数越来越少,最终可能会变得过时。 - Brett Ryan
5
这是Apache CLI项目的“使用场景”页面,详细介绍如何快速开始使用它:http://commons.apache.org/cli/usage.html - Brad Parks
21
@RemkoPopma,您的picocli库看起来非常棒,感谢您的付出。但是,我认为您在这里和其他帖子中所做的事情(编辑接受的答案并将您的库推广到其顶部,甚至没有透露这是一个编辑而不是原始作者发布的内容)是对您的管理能力的可怕滥用。我向其他管理员举报此事。 - Alexander Malakhov
2
@AlexanderMalakhov 我想纠正一件事情:任何人都可以编辑(不需要审核权限),并且鼓励编辑以保持帖子的相关性和最新性(当前答案已经有10年了)。话虽如此,好的编辑应该平衡,避免被视为垃圾邮件,并且必须披露关联。感谢您指出这一点。 - Remko Popma
显示剩余5条评论

326

看看更新更多的JCommander

我创建了它。 我很高兴收到问题或功能请求。


9
很高兴您喜欢 JCommander :-) 我不想在如何处理标志的语义上添加太多内容,所以您只需要在使用的注释中添加同义词即可:@Parameter(names = { "-h", "--help" })我认为这是一个合理的妥协。 - Cedric Beust
15
好工具。功能强大、灵活,而且你不必处理烦人的传统选项解析器。 - Ian Gilham
3
非常感谢您的夸奖。我认为如果我写命令行参数解析器的话,我也会像你编写JCommander一样。做得非常好! - SRG
10
@CedricBeust,这是一个非常棒的库,非常感谢你。由于我们可以定义自己的Args类并将其传递而不依赖于任何库的类,因此它非常灵活。 - Brett Ryan
3
轻松超越竞争对手! - Marcus Junius Brutus
显示剩余12条评论

260

8
嘿 @Ben Flynn,里面有一些相当惊奇和有趣形状的车轮。我猜这是一种基本无害的方式来表明有许多不同的方法来完成它! - lexicalscope
18
我注意到 JOpt Simple 的作者维护了一个非常相似的列表!我们需要做的是将这些列表转化为一个表格,列出特性和关注点,这样我们这些可怜的用户才能做出明智的选择。 - Tom Anderson
1
我已经构建了Rop - github.com/ryenus/rop。它具有注解解决方案,您可以通过普通类和字段声明命令和选项,几乎是一种声明性的方式来构建命令行解析器。它可以构建类似Git(单命令)或Maven(多命令)的应用程序。 - ryenus
15
列出的项目大部分都已经被放弃维护了。经过筛选,我认为那些备受关注且正在积极维护的项目包括:commons-cli、jcommander、args4j、jopt-simple和picocli。对于argparse4j和cli-parser等项目的作者,抱歉我不得不进行了一些主观排序,选择了前五个,但显然列表中其他项目也很受欢迎并仍在积极开发中。 - George Hawkins
4
我向大家发起挑战,要求在每个解析器的说明中包含最后一个稳定版本的发布日期。 - Sledge
显示剩余4条评论

116

现在是2022年,是时候比Commons CLI更好了... :-)

您应该构建自己的Java命令行解析器,还是使用库?

许多类似小型实用程序的应用程序可能会自己开发命令行解析功能,以避免增加外部依赖。 Picocli 可能是一个有趣的选择。

Picocli是一个现代化的库和框架,可轻松构建功能强大、用户友好、启用GraalVM的命令行应用程序。它只有1个源文件,因此应用程序可以将其包含为源代码以避免添加依赖项。

它支持颜色、自动完成、子命令等功能。编写于Java中,可从Groovy、Kotlin、Scala等中使用。

Minimal usage help with ANSI colors

特性:

  • 基于注释: 声明式, 避免重复 以及表达程序员意图
  • 方便: 使用 一行代码 解析用户输入并运行业务逻辑
  • 强类型 一切-命令行选项以及位置参数
  • POSIX 簇短选项 (<command> -xvfInputFile 以及 <command> -x -v -f InputFile)
  • 精细控制: 允许最小、最大和可变数量的参数的arity模型,例如,"1..*", "3..5"
  • 子命令 (可以嵌套到任意深度)
  • 功能丰富: 可组合的参数组、分割引号参数、可重复的子命令等等
  • 易用性强:使用帮助消息中使用颜色对重要元素进行对比,例如选项名称与其余使用帮助的区别,以减少用户的认知负担
  • 将您的应用程序分发为GraalVM本地镜像
  • 兼容Java 5及更高版本
  • 详细而严谨的文档
  • 使用帮助消息可以通过注释轻松自定义(无需编程)。例如:

    扩展使用帮助消息 (源代码)

    我忍不住再添加一个截图来展示使用帮助消息的可能性。使用帮助是您的应用程序面向用户的一面,所以要有创意,尽情享受!

    picocli演示

    免责声明:我创建了picocli。非常欢迎反馈或问题。


    8
    太聪明了!可惜这个答案被埋没在底部。 Apache Commons CLI 冗长、有 bug,而且已经很长时间没有更新了。我也不想使用 Google 的命令行解析器,因为我不想基于我的命令行参数使用历史获得定向广告。但看起来它比 picocli 更冗长一些。 - Pete
    4
    我同意@Pete的观点...我看完上面的列表,浪费了很多时间,但是这个被埋在最底下。这应该成为首选答案。做得好! Apache CLI或大多数其他解析器无法满足我的需求。它们即使对于picocli也很具有挑战性,但它可以给我最接近我想要的语法/行为,并且足够灵活,可以进行自定义。作为一个额外的奖励,由于ANSI的东西,它看起来很棒。 - Shai Almog
    1
    @ShaiAlmog 最受欢迎的答案已经有10年历史了,已经过时了。我认为在2019年推荐Commons CLI是误导人的。请考虑重写最佳答案,使其更加现代化。 - Remko Popma
    1
    谢谢Remko(比我高一个级别)! 我尝试了你的picocli,并且能够在我的工具中使用它。现在我真的很高兴(以前在commons-cli中,我总是不得不编写一个包装器来保持代码清洁:多一个额外类,太多额外的代码)。 - jazz64
    1
    在我发现这个库(picocli)之前,我认为除了cobra和golang之外,没有更好的选项来创建cli程序,现在情况不同了,有另一个强大的cli程序选项(当然我指的是picocli和java)。 - SinaMobasheri
    显示剩余4条评论

    24

    最近有人指向了我一个args4j,它是基于注解的。我非常喜欢它!


    1
    +1 for Args4J!非常人性化、灵活和易懂。我认为它应该成为构建Java CLI应用程序的标准库。 - Zearin
    很棒的是它可以处理无序(按字段顺序排序)的使用打印,而JCommander不能,而且更加灵活。 - Daniel Hári
    @DanielHári 只是提供信息,这个功能是在JCommander中添加的(大约在2017年2月底)。 - Turtle
    建议:您可能想在您的回答中添加一个示例,这比仅提供外部链接更有帮助。 - not2savvy

    23
    我使用过JOpt,并发现它非常方便:http://jopt-simple.sourceforge.net/ 首页还提供了大约8个备选库的列表,请查看并选择最符合你需求的那一个。

    15

    我知道这里的大多数人会找出无数理由来反对我的方式,但没关系。我喜欢保持简单,所以我只是用 "=" 将键与值分开,并将它们存储在 HashMap 中,就像这样:

    Map<String, String> argsMap = new HashMap<>();
    for (String arg: args) {
        String[] parts = arg.split("=");
        argsMap.put(parts[0], parts[1]);
    } 
    

    你可以始终维护一个期望的参数列表,以帮助用户在忘记参数或使用错误参数时... 如果你想要太多功能,那么这个解决方案也不适合你。


    12

    这是作为Bazel项目的一部分开源的Google命令行解析库。个人认为它是最好的,比Apache CLI要简单得多。

    https://github.com/pcj/google-options

    安装

    Bazel

    maven_jar(
        name = "com_github_pcj_google_options",
        artifact = "com.github.pcj:google-options:jar:1.0.0",
        sha1 = "85d54fe6771e5ff0d54827b0a3315c3e12fdd0c7",
    )
    

    Gradle

    dependencies {
      compile 'com.github.pcj:google-options:1.0.0'
    }
    

    Maven

    <dependency>
      <groupId>com.github.pcj</groupId>
      <artifactId>google-options</artifactId>
      <version>1.0.0</version>
    </dependency>
    

    使用方法

    创建一个继承OptionsBase的类,并定义您的@Option

    package example;
    
    import com.google.devtools.common.options.Option;
    import com.google.devtools.common.options.OptionsBase;
    
    import java.util.List;
    
    /**
     * Command-line options definition for example server.
     */
    public class ServerOptions extends OptionsBase {
    
      @Option(
          name = "help",
          abbrev = 'h',
          help = "Prints usage info.",
          defaultValue = "true"
        )
      public boolean help;
    
      @Option(
          name = "host",
          abbrev = 'o',
          help = "The server host.",
          category = "startup",
          defaultValue = ""
      )
      public String host;
    
      @Option(
        name = "port",
        abbrev = 'p',
        help = "The server port.",
        category = "startup",
        defaultValue = "8080"
        )
        public int port;
    
      @Option(
        name = "dir",
        abbrev = 'd',
        help = "Name of directory to serve static files.",
        category = "startup",
        allowMultiple = true,
        defaultValue = ""
        )
        public List<String> dirs;
    
    }
    

    解析参数并使用它们。

    package example;
    
    import com.google.devtools.common.options.OptionsParser;
    import java.util.Collections;
    
    public class Server {
    
      public static void main(String[] args) {
        OptionsParser parser = OptionsParser.newOptionsParser(ServerOptions.class);
        parser.parseAndExitUponError(args);
        ServerOptions options = parser.getOptions(ServerOptions.class);
        if (options.host.isEmpty() || options.port < 0 || options.dirs.isEmpty()) {
          printUsage(parser);
          return;
        }
    
        System.out.format("Starting server at %s:%d...\n", options.host, options.port);
        for (String dirname : options.dirs) {
          System.out.format("\\--> Serving static files at <%s>\n", dirname);
        }
      }
    
      private static void printUsage(OptionsParser parser) {
        System.out.println("Usage: java -jar server.jar OPTIONS");
        System.out.println(parser.describeOptions(Collections.<String, String>emptyMap(),
                                                  OptionsParser.HelpVerbosity.LONG));
      }
    
    }
    

    https://github.com/pcj/google-options


    嗨,保罗。当我阅读你的回答或项目文档时,我不知道它可以处理什么样的命令行。例如,你可能提供类似于 myexecutable -c file.json -d 42 --outdir ./out 的内容。而我并没有看到你如何定义短/长/描述选项...祝好! - oHo

    9

    8

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接