一、Java 命令行开发方案

什么是命令行程序?

命令行程序俗称 CLI(Command Line Interface),是指通过命令行界面运行的应用程序。通过终端窗口接收用户输入的 纯文本 命令,并执行相应的任务。

一些常见的命令行环境包括 Unix/Linux 的终端、Windows 的命令提示符和 PowerShell 等。学编程的同学可能没有开发过命令行程序,但一定都接触过终端!

命令的结构

输入给命令行的命令通常包含:

  • command:命令类型,具体要做的事
  • option:选项,用于改变命令的行为
  • parameter:参数,传递给命令行工具的值

为什么要开发命令行?

命令行程序的几个优点:

  • 不依赖于特定的图形界面,非常轻量
  • 通常可以直接在操作系统自带的终端环境中运行
  • 可以和用户交互、给用户输入引导和帮助手册
  • 内置一些快捷操作(比如查看历史命令、上下切换命令)

还有一个最大的优点 —— 简单直接,比如复制粘贴别人写好的命令就能执行,而不用像使用网页一样点击多次,非常符合程序员的使用(偷懒)习惯,less is more!

命令行的作用

回归到我们的项目,命令行的作用是什么呢?

可以使用命令行程序来和用户交互,引导用户输入代码生成的定制参数,并将输入参数封装为配置对象,然后 “喂” 给之前编写好的代码生成器来生成文件。

比如之间我们的动态模板配置 MainTemplateConfig 中包含 loop、author、outputText 这 3 个参数,那么可以让用户输入下列完整命令,来给模板配置传值:

      generate --loop --author yupi --outputText good
    

或者更人性化一些,允许用户交互式输入,比如先输入 generate,然后按照系统的提示依次输入其他参数,如下图:

image.png

实现方案

了解了命令行的作用后,我们如何用 Java 语言开发命令行程序呢?

自主实现

最直接的方案就是使用 Java 内置的类库自主实现,比如通过 Scanner 读取用户的输入,再配合 while、if … else 等流程控制语句实现多次输入和提示。

示例代码如下:

      Scanner scanner = new Scanner(System.in);

while (scanner.hasNext()) {
    // 读取整数
    int number = scanner.nextInt();
}
    

这种方式虽然简单,但缺点也很多:

1)需要自主解析用户的输入

前面已经介绍了命令的结构,可以看到一句命令可能非常复杂,包含各种选项和参数,而且用户还可能乱序输入不同的参数。

image.png

如何从这么一句复杂的命令中提取出需要的值,是一个难题。

2)需要自主编写一套获取参数的交互流程

比如用户输入了异常值怎么办?是中断程序还是给出提示?给出什么提示?这些都要开发者自己考虑。

3)需要自主实现更多高级功能

比如基本所有命令行工具都有的帮助手册命令(–help)、颜色高亮等。

如果这些和业务无关的功能如果都需要自己开发,那真的是太浪费时间了!

所以建议直接用现成的第三方命令行开发库。

第三方库

这里鱼皮做了充分的调研,收集了几种经典的 Java 命令行开发相关库,简单分为 3 类:

1)命令行工具开发框架

专门用于开发命令行工具的框架

⭐️ Picocli(https://github.com/remkop/picocli):优点是 GitHub 的 star 数多(4k+)、持续更新,支持颜色高亮、自动补全、子命令、帮助手册等,最推荐。

2)控制台输入处理库

能够对用户在控制台的输入进行处理的库

JLine(https://github.com/jline/jline3):支持自动补全、行编辑、查看命令历史等,但官方文档内容略少、学习成本高。参考教程:https://zhuanlan.zhihu.com/p/43835406

3)命令行解析库

支持对命令行进行解析取值的库

⭐️ JCommander(https://github.com/cbeust/jcommander):注解驱动,可以直接把命令映射到对象上,从而大幅简化代码。GitHub 上近 2k star,比较推荐。

image.png

Apache Commons CLI(https://github.com/apache/commons-cli):简单易用,但是功能不够多。参考教程:https://blog.csdn.net/liuxiangke0210/article/details/78141887

Jopt Simple(https://github.com/jopt-simple/jopt-simple):不推荐。冷门、很久没维护、star 数少、生态不好。


综上,最推荐的是专业的命令行开发框架 Picocli,下面从 0 开始带大家入门和实战这个框架。

二、Picocli 命令行框架入门

网上有关 Picocli 框架的教程非常少,最推荐的入门方式除了看鱼皮的教程外,就是阅读官方文档了。

官方文档:https://picocli.info/

推荐从官方提供的快速入门教程开始:https://picocli.info/quick-guide.html

一般我们学习新技术的步骤是:先跑通入门 Demo,再学习该技术的用法和特性。

入门 Demo

1)在 yuzi-generator-basic 项目的 pom.xml 文件中引入 picocli 的依赖:

      <!-- https://picocli.info -->
<dependency>
    <groupId>info.picocli</groupId>
    <artifactId>picocli</artifactId>
    <version>4.7.5</version>
</dependency>
    

然后我们在 com.yupi 包下新建 cli.example 包,用于存放所有和 Picocli 入门有关的示例代码。

2)复制官方快速入门教程中的示例代码到 com.yupi.cli.example 包下,并略微修改 run 方法中的代码,打印参数的值。

完整代码如下:

      package com.yupi.cli.example;

import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;

@Command(name = "ASCIIArt", version = "ASCIIArt 1.0", mixinStandardHelpOptions = true) 
public class ASCIIArt implements Runnable { 

    @Option(names = { "-s", "--font-size" }, description = "Font size") 
    int fontSize = 19;

    @Parameters(paramLabel = "<word>", defaultValue = "Hello, picocli", 
               description = "Words to be translated into ASCII art.")
    private String[] words = { "Hello,", "picocli" }; 

    @Override
    public void run() {
        // 自己实现业务逻辑
        System.out.println("fontSize = " + fontSize);
        System.out.println("words = " + String.join(",", words));
    }

    public static void main(String[] args) {
        int exitCode = new CommandLine(new ASCIIArt()).execute(args); 
        System.exit(exitCode); 
    }
}
    

看不懂这段代码没关系,官方文档已经给了非常详细的解释:

image.png

帮大家翻译一下:

  1. 创建一个实现 RunnableCallable 接口的类,这就是一个命令。
  2. 使用 @Command 注解标记该类并为其命名,mixinStandardHelpOptions 属性设置为 true 可以给应用程序自动添加 --help--version 选项。
  3. 通过 @Option 注解将字段设置为命令行选项,可以给选项设置名称和描述。
  4. 通过 @Parameters 注解将字段设置为命令行参数,可以指定默认值、描述等信息。
  5. Picocli 会将命令行参数转换为强类型值,并自动注入到注解字段中。
  6. 在类的 runcall 方法中定义业务逻辑,当命令解析成功(用户敲了回车)后被调用。
  7. main 方法中,通过 CommandLine 对象的 execute 方法来处理用户输入的命令,剩下的就交给 Picocli 框架来解析命令并执行业务逻辑啦~
  8. CommandLine.execute 方法返回一个退出代码。可以调用 System.exit 并将该退出代码作为参数,从而向调用进程表示成功或失败。

3)让我们更改主程序的执行参数(args)来测试程序,能够成功看到输出结果,如下图:

image.png

通过这个入门 Demo,我们可以简单总结一个命令的开发流程:

  1. 创建命令
  2. 设置选项和参数
  3. 编写命令执行的业务逻辑
  4. 通过 CommandLine 对象接受输入并执行命令

在跑通了入门 Demo 后,我们来学习一些 Picocli 开发命令行的实用功能。

实用功能

帮助手册

通过给类添加的 @Command 注解参数 mixinStandardHelpOptions 设置为 true 来开启:

      @Command(name = "ASCIIArt", mixinStandardHelpOptions = true) 
    

然后将主程序的输入参数设置为 --help 就能打印出命令的帮助手册信息了,如下图:

image.png

可以看到,Picocli 生成的帮助手册不仅规范、而且清晰完整。

命令解析

Picocli 最核心的能力就是命令解析,能够从一句完整的命令中解析选项和参数,并填充到对象的属性中。

Picocli 使用注解的方式实现命令解析,不需要自己编写代码,整个类看起来非常清晰。

最核心的 2 个注解其实在入门 Demo 中我们已经使用到了:

  • @Option 注解用于解析选项
  • @Parameters 注解用于解析参数

image.png

示例代码如下:

      @Option(names = { "-s", "--font-size" }, description = "Font size") 
int fontSize = 19;

@Parameters(paramLabel = "<word>", defaultValue = "Hello, picocli", 
           description = "Words to be translated into ASCII art.")
private String[] words = { "Hello,", "picocli" }; 
    

可以给这些注解指定参数,比较常用的参数有:

1)@Option 注解的 names 参数:指定选项英文名称。

2)description 参数:指定描述信息,从而让生成的帮助手册和提示信息更清晰。

3)@Parameters 注解的 paramLabel 参数:参数标签,作用类似于描述信息。

4)@Parameters 注解的 defaultValue 参数:默认值,参考文档:https://picocli.info/#_default_values

5)required 参数:要求必填,参考文档:https://picocli.info/#_required_arguments

示例代码如下:

      class RequiredOption {
    
    @Option(names = "-a", required = true)
    String author;
}
    

此外,命令解析天然支持 多值选项 ,只需要把对象属性的类型设置为数组类型即可,比如:

      @Option(names = "-option")
int[] values;
    

具体可以参考官方文档:https://picocli.info/#_multiple_values

更多关于选项和参数注解的用法,也可以阅读官方文档学习:https://picocli.info/quick-guide.html#_options_and_parameters

交互式输入

所谓的交互式输入就是允许用户像跟程序聊天一样,在程序的指引下一个参数一个参数地输入。

如下图:

image.png

Picocli 为交互式输入提供了很好的支持,我梳理了大概 4 种交互式输入的模式。

1)基本能力

交互式输入的一个典型应用场景就是:用户要登录时,引导 ta 输入密码。

官方已经为我们提供了一段交互式输入的示例代码,鱼皮对它进行了简化,示例代码如下:

参考官方文档:https://picocli.info/#_interactive_password_options

      package com.yupi.cli.example;

import picocli.CommandLine;
import picocli.CommandLine.Option;

import java.util.concurrent.Callable;

public class Login implements Callable<Integer> {
    @Option(names = {"-u", "--user"}, description = "User name")
    String user;

    @Option(names = {"-p", "--password"}, description = "Passphrase", interactive = true)
    String password;

    public Integer call() throws Exception {
        System.out.println("password = " + password);
        return 0;
    }

    public static void main(String[] args) {
        new CommandLine(new Login()).execute("-u", "user123", "-p");
    }
}
    

让我们分析下上面的代码,主要包含 4 个部分:

1)首先命令类需要实现 Callable 接口

      public class Login implements Callable<Integer> {
	...
}
    

2)将 @Option 注解的 interactive 参数设置为 true,表示该选项支持交互式输入

      @Option(names = {"-p", "--password"}, interactive = true)
String password;
    

3)在所有参数都输入完成后,会执行 call 方法,可以在该方法中编写具体的业务逻辑:

      public Integer call() throws Exception {
    System.out.println("password = " + password);
    return 0;
}
    

4)在 Main 方法中执行命令并传入参数:

      java

new CommandLine(new Login()).execute("-u", "user123", "-p");
    

执行上述代码,看到程序提示我们输入密码:

image.png

注意,如果以 jar 包方式运行上述程序,用户的输入默认是不会显示在控制台的(类似输入密码时的体验)。从 Picocli 4.6 版本开始,可以通过指定 @Option 注解的 echo 参数为 true 来显示用户的输入,并通过 prompt 参数指定引导用户输入的提示语。

2)多个选项交互式

Picocli 支持在一个命令中指定多个交互式输入的选项,会按照顺序提示用户并接收输入。

在上述代码中再增加一个 checkPassword 选项,同样开启交互式输入,代码如下:

      public class Login implements Callable<Integer> {
    @Option(names = {"-u", "--user"}, description = "User name")
    String user;

    @Option(names = {"-p", "--password"}, description = "Passphrase", interactive = true)
    String password;

    @Option(names = {"-cp", "--checkPassword"}, description = "Check Password", interactive = true)
    String checkPassword;

    public Integer call() throws Exception {
        System.out.println("password = " + password);
        System.out.println("checkPassword = " + checkPassword);
        return 0;
    }

    public static void main(String[] args) {
        new CommandLine(new Login()).execute("-u", "user123", "-p");
    }
}
    

但运行上述代码我们会发现,怎么只提示我输入了密码,没提示我输入确认密码呢?

image.png

这是由于 Picocli 框架的规则,用户必须在命令中指定需要交互式输入的选项(比如 -p),才会引导用户输入。

所以我们需要修改上述代码中的 main 方法,给命令输入补充 -cp 参数:

      public static void main(String[] args) {
    new CommandLine(new Login()).execute("-u", "user123", "-p", "-cp");
}
    

再次执行,这下程序会依次提醒我们输入两个选项啦:

image.png

根据实际使用情况,又可以将交互式输入分为 2 种模式:

  • 可选交互式:用户可以直接在整行命令中输入选项,而不用给用户提示信息。
  • 强制交互式:用户必须获得提示并输入某个选项,不允许不填写。

下面分别讲解这两种模式。

加载中...

声明

作者: liyao

版权:本博客所有文章除特别声明外,均采用CCBY-NC-SA4.O许可协议。转载请注明!

最后更新于 2026-02-18 18:15 history