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主要包括:
主要是借助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中语法解析的支持:
基于上一步解析后,会将参数解析成CommandLine,结合Option中的配置,需要我们完成各种配置、参数匹配后的业务处理流程,类型下面这样:
if( commandLine.hasOption("help") ){
helper.printHelp("calendar [options] nnwhere 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] nnwhere 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都可以提供帮助。