CLI
CLI,全称为命令行界面(Command Line Interface),是一种用于通过键盘输入指令与操作系统进行交互的软件机制。这种界面是在图形用户界面得到普及之前使用最为广泛的用户界面,并且, 即使在当前图形用户界面广泛使用的环境下,CLI仍然有其独特的优势和广泛的应用。
对于CLI,它的一个重要特性就是效率。用户可以在一条文本命令中对多个文件执行操作,而不需要在各个窗口之间切换,节省了大量时间。此外,如果你已经熟悉了这些命令,那么你可以非常快速地浏览系统并与之交互。
构建CLI的工具很多,今天主要基于Java语言来实现,其中Apache Commons CLI框架提供了这样的便利。今天结合之前学习的graalVM提供的native-image工具,来生成一个exe类型的可执行文件,由于graalVM的跨平台性,我们还能生成各个平台的CLI命令来辅助完成更多的工作。
Apache Commons CLI是一个用于编写命令行界面的Java库。它提供了一个灵活的框架,可以很容易地定义和解析命令行参数。这个库的主要优点是它可以处理各种类型的参数,包括选项、位置参数、可选参数等。
下面以native-image
为例,通过在终端输入native-image --help可以看到以下信息
_>?native-image?--help
GraalVM?Native?Image?(https://www.graalvm.org/native-image/)
This?tool?can?ahead-of-time?compile?Java?code?to?native?executables.
Usage:?native-image?[options]?class?[imagename]?[options]
???????????(to?build?an?image?for?a?class)
???or??native-image?[options]?-jar?jarfile?[imagename]?[options]
???????????(to?build?an?image?for?a?jar?file)
???or??native-image?[options]?-m?<module>[/<mainclass>]?[options]
???????native-image?[options]?--module?<module>[/<mainclass>]?[options]
???????????(to?build?an?image?for?a?module)
where?options?include:
????@argument?files???????one?or?more?argument?files?containing?options
????-cp?<class?search?path?of?directories?and?zip/jar?files>
????-classpath?<class?search?path?of?directories?and?zip/jar?files>
????--class-path?<class?search?path?of?directories?and?zip/jar?files>
??????????????????????????A?;?separated?list?of?directories,?JAR?archives,
??????????????????????????and?ZIP?archives?to?search?for?class?files.
????-p?<module?path>
????--module-path?<module?path>...
??????????????????????????A?;?separated?list?of?directories,?each?directory
??????????????????????????is?a?directory?of?modules.
一个合格的CLI基本都会提供help选项来展示,这个CLI的语法、选项以及功能描述。从上面的输出可以看到help主要包括:
介绍:主要对命令的功能的描述,包括官网、版本以及一些内在系数等
用法:包括命令语法格式、配置项、参数等信息
参数说明:具体配置项参数的说明,以及具体的功能描述
定义阶段:在Java代码中定义Option参数,定义参数、是否需要输入值、简单的描述等
解析阶段:应用程序传入参数后,CLI进行解析
询问阶段:通过查询CommandLine询问进入到哪个程序分支中
主要是借助Option类提供的API来构建各种选项以及参数信息,下面是对应API的描述:
返回值 | 方法名 | 说明 |
---|---|---|
Options | addOption(Option opt) | 添加一个选项实例 |
Options | addOption(String opt, boolean hasArg, String description) | 添加一个只包含短名称的选项 |
Options | addOption(String opt, String description) | 添加一个只包含短名称的选项 |
Options | addOption(String opt, String longOpt, boolean hasArg, String description) | 添加一个包含短名称和长名称的选项 |
Options | addOptionGroup(OptionGroup group) | 添加一个选项组 |
List | getMatchingOptions(String opt) | 获得匹配选项的长名称集合 |
Option | getOption(String opt) | 通过长名称或短名称获得选项 |
OptionGroup | getOptionGroup(Option opt) | 获得选项所在的选项组 |
Collection | getOptions() | 获得一个只读的选项集合 |
List | getRequiredOptions() | 获得必须的选项集合 |
boolean | hasLongOption(String opt) | 判断是否存在选项 |
boolean | hasOption(String opt) | 判断是否存在选项 |
boolean | hasShortOption(String opt) | 判断是否存在选项 |
主要对输入参数的解析,也就是main方法的参数,默认提供下面3中语法解析的支持:
DefaultParser:提供了基础的解析功能,能解析简单的命令行参数。(比如:java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo)
PosixParser:提供了解析POSIX形式参数的功能。(比如:tar -zxvf foo.tar.gz)
GnuParser:提供了解析长参数及Java命令中参数的功能。(比如:du --human-readable --max-depth=1)
基于上一步解析后,会将参数解析成CommandLine,结合Option中的配置,需要我们完成各种配置、参数匹配后的业务处理流程,类型下面这样:
??????if(?commandLine.hasOption("help")?){
??????????helper.printHelp("calendar?[options]?\n\nwhere?options?include:",?null,?options,?null,?false);
??????????return;
??????}
??????if(?commandLine.hasOption("version")?){
??????????printResult("1.0.0");
??????????return;
??????}
解析的过程有时候会比较些复杂,示例中是针对单一选项的分支,当多个选项混合使用时,比如tar -zxvf xxx.tar.gz
这样的,当然前提是我们定义的CLI支持这种风格。
下面通过一个简单的示例看下如何构建一个CLI的工具:该示例的作用是按指定格式输出当前日期:
clendar?-o?yyyy-MM-dd
定义配置项
????private?static?Options?initOptions()?{
????????Options?options?=?new?Options();
????????options.addOption(Option.builder("H")
????????????????.longOpt("help")
????????????????.desc("show?help?information").build());
????????options.addOption(Option.builder("V")
????????????????.longOpt("version")
????????????????.desc("show?help?information").build());
????????options.addOption(Option.builder("O")
????????????????.longOpt("out")
????????????????.hasArg(true)
????????????????.argName("fmt")?//?只是定义
????????????????.required(false)
????????????????.desc("configure?the?date?output?format").build());
????????return?options;
????}
解析参数
????private?static?CommandLine?parseArguments(Options?options,?String[]?args){
????????CommandLineParser?parser?=?new?DefaultParser();
????????try?{
????????????return?parser.parse(options,?args);
????????}?catch?(ParseException?e)?{
????????????System.err.println(e.getMessage());
????????}
????????return?null;
????}
询问阶段
????private?static?void?handleCommand(Options?options,?CommandLine?commandLine)?{
????????if(ArrayUtils.isEmpty(commandLine.getOptions())?){
????????????printResult("Please?specify?options?for?calendar?building?or?use?--help?for?more?info.");
????????????return;
????????}
????????if(?commandLine.hasOption("help")?){
????????????helper.printHelp("calendar?[options]?\n\nwhere?options?include:",?null,?options,?null,?false);
????????????return;
????????}
????????if(?commandLine.hasOption("version")?){
????????????printResult("1.0.0");
????????????return;
????????}
????????if(?commandLine.hasOption("out")?){
????????????String?fmt?=?commandLine.getOptionValue("out");
????????????if(StringUtils.isEmpty(fmt)){
????????????????fmt?=?"yyyy-MM-dd?HH:mm:ss";
????????????}
????????????printResult(DateFormatUtils.format(new?Date(),?fmt));
????????????return;
????????}
????????//?calendar:?'x'?is?not?a?git?command.?See?'calendar?--help'.
????????helper.printHelp(String.format("calendar:?'%s'?is?not?a?calendar?command.?See?'calendar?--help'.",?Arrays.toString(commandLine.getArgs())),?options,?false);
????}
定义程序入口:
????public?static?void?main(String[]?args)?{
????????//?定义阶段
????????Options?options?=?initOptions();
????????//?解析阶段
????????CommandLine?commandLine?=?parseArguments(options,?args);
????????//?询问阶段
????????handleCommand(options,?commandLine);
????}
这里我们引入maven-assembly-plugin
插件,主要帮助在打包时将依赖包一并写入jar文件,同时将入口文件定义到manifest:
<plugin>
????<groupId>org.apache.maven.plugins</groupId>
????<artifactId>maven-assembly-plugin</artifactId>
????<version>3.3.0</version>
????<executions>
????????<execution>
????????????<id>package-jar-with-dependencies</id>
????????????<phase>package</phase>
????????????<goals>
????????????????<goal>single</goal>
????????????</goals>
????????????<configuration>
????????????????<archive>
????????????????????<manifest>
????????????????????????<mainClass>${main-class}</mainClass>
????????????????????</manifest>
????????????????</archive>
????????????????<descriptorRefs>
????????????????????<!--?bin,jar-with-dependencies,src,project?-->
????????????????????<descriptorRef>jar-with-dependencies</descriptorRef>
????????????????</descriptorRefs>
????????????</configuration>
????????</execution>
????</executions>
</plugin>
可以直接通过maven插件或者下的命令,将上面的代码打包成jar文件
mvn?clean?package
如果安装上面的配置,最终会在项目target目录输出一个以jar-with-dependencies为后缀的jar文件,通过下面的命令可以测试cli命令
java?-jar?./target/calendar-jar-with-dependencies.jar?-h?
这样的CLI可不是我们想要的,一来需要依赖JRE的运行环境,同时调用极其不方便。
如果你看过之前的文章,关于GraalVM的使用,按照文档下载并配置好运行环境后,可以通过下面的命令对上一步的jar文件进一步处理
native-image -jar [jar] -o [name]
native-image?-jar?./target/calendar-jar-with-dependencies.jar?-o?calendar
通过上面的命令会生成一个calendar.exe文件,这样将其加入到环境变量后,则可以在windows平台终端上使用了
对于不喜欢直接使用命令的,当然这里也可以使用插件exec-maven-plugin
,在maven生命周期package阶段,自动执行上面的命令,这样整个过程只需要执行mvn clean package即可
<plugin>
????<groupId>org.codehaus.mojo</groupId>
????<artifactId>exec-maven-plugin</artifactId>
????<version>3.1.0</version>
????<executions>
????????<execution>
????????????<id>native-image-app</id>
????????????<phase>package</phase>
????????????<goals>
????????????????<goal>exec</goal>
????????????</goals>
????????????<configuration>
????????????????<environmentVariables>
????????????????</environmentVariables>
????????????????<!--?native-image?-jar?./target/tool-jar-with-dependencies.jar?-o?tool?-->
????????????????<executable>native-image</executable>
????????????????<arguments>
????????????????????<argument>-jar</argument>
????????????????????<argument>${project.basedir}/target/${project.build.finalName}-jar-with-dependencies.jar</argument>
????????????????????<argument>-o</argument>
????????????????????<argument>${project.build.finalName}</argument>
????????????????</arguments>
????????????</configuration>
????????</execution>
????</executions>
</plugin>
在终端执行下面的命令接口看到预期的结果:
calendar.exe?-O?yyyy-MM-dd
总的来说,Apache Commons CLI是一个非常强大的工具,可以帮助你轻松地处理命令行参数。无论你的应用程序需要处理多少个参数,或者这些参数的类型是什么, Commons CLI都可以提供帮助。