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主要包括:
- 介紹:主要對命令的功能的描述,包括官網、版本以及一些內在系數等
- 用法:包括命令語法格式、配置項、參數等信息
- 參數說明:具體配置項參數的說明,以及具體的功能描述
Common-CLI
- 定義階段:在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] 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
測試jar
如果安裝上面的配置,最終會在項目target目錄輸出一個以jar-with-dependencies為后綴的jar文件,通過下面的命令可以測試cli命令
java -jar ./target/calendar-jar-with-dependencies.jar -h
這樣的CLI可不是我們想要的,一來需要依賴JRE的運行環境,同時調用極其不方便。
生成exe
如果你看過之前的文章,關于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>
測試exe
在終端執行下面的命令接口看到預期的結果:
calendar.exe -O yyyy-MM-dd
總結
總的來說,Apache Commons CLI是一個非常強大的工具,可以幫助你輕松地處理命令行參數。無論你的應用程序需要處理多少個參數,或者這些參數的類型是什么, Commons CLI都可以提供幫助。