Apache Commons CLI 簡介
1. 概述
在本教程中,我們將探索 Java Apache Commons CLI 程式庫。它是一個框架,可以幫助開發人員以高效、標準的方式建立現有軟體工具的命令列介面 (CLI)。
該程式庫支援定義 CLI 選項及其基本驗證,可加快 CLI 的開發速度。它有助於解析命令列參數及其值。最後,參數值可以傳遞給實作該工具的底層服務。
值得注意的是,Apache Commons CLI 庫也用於 Apache 的多個產品,包括 Kafka、Maven、Ant 和 Tomcat。
我們將討論 Apache Commons CLI 中的一些重要類,然後在範例程式中使用它們來展示其功能。
2. CLI 的主要關注點
CLI 透過協助自動化執行一系列與其領域相關的任務,為工具提供了優勢。此外,在當今世界,DevOps 工程師在沒有 CLI 的情況下工作是不可想像的。
除了工具底層實現的挑戰之外,所有 CLI 都需要處理一些基本要求:
- 解析命令列參數,提取參數值,並將它們傳遞給底層服務
- 以一定的格式顯示幫助訊息
- 顯示版本
- 處理缺少的必需選項
- 處理未知選項
- 處理互斥選項
3. 重要課程
讓我們來看看Apache Commons CLI 函式庫的重要類別:
Option 、 OptionGroup和Options類別幫助定義 CLI 。所有 CLI 選項的定義都包含在Options類別中。 CommandLineParser類別的parse()方法使用Options類別來解析命令列。如果出現任何偏差, parse()方法會拋出適當的異常。解析後,可以進一步探測CommandLine類別以提取 CLI 選項的值(如果有)。
最後,提取的值可以傳遞給實作 CLI 工具的底層服務。
與CommandLineParser類別中的parse()方法類似, HelpFormatter也使用Options類別來顯示 CLI 工具的說明文字。
4. 實施
讓我們進一步探索 Apache Commons CLI 庫類,並了解它們如何幫助一致、快速地建立 CLI 工具。
4.1.先決條件 Maven 依賴項
首先,讓我們在pom.xml檔案中加入必要的Maven 依賴項:
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.6.0</version>
</dependency>
4.2.定義、解析和探測命令列參數
考慮使用psql CLI 連接到 PostgreSQL 資料庫的命令:
psql -h PGSERVER -U postgres -d empDB
或者:
psql --host PGSERVER -username postgres -dbName empDB
這兩個指令都需要資料庫伺服器主機、使用者名稱和資料庫名稱的輸入參數。第一個指令使用短選項名稱,而第二個指令使用長選項名稱。 username和dbName是必要選項,而host是可選的。如果缺少主機,則預設情況下,我們將 localhost 視為主機值。
現在,讓我們定義、解析和探測命令列參數:
@Test
void whenCliOptionProvided_thenParseAndExtractOptionAndArgumentValues() throws ParseException {
Options options = new Options();
Option hostOption = createOption("h", "host", "HOST", "Database server host", false);
Option userNameOption = createOption("U", "username", "USERNAME", "Database user name", true);
Option dbNameOption = createOption("d", "dbName", "DBNAME", "Database name to connect to", true);
options.addOption(hostOption)
.addOption(dbNameOption)
.addOption(userNameOption);
String[] commandWithShortNameOptions = new String[] { "-h", "PGSERVER", "-U", "postgres", "-d", "empDB" };
parseThenProcessCommand(options, commandWithShortNameOptions, "h", "U", "d" );
String[] commandWithLongNameOptions = new String[] { "--username", "postgres", "--dbName", "empDB" };
parseThenProcessCommand(options, commandWithShortNameOptions, "host", "username", "dbName" );
}
為了定義命令中的選項,我們透過呼叫方法createOption()建立了與每個輸入選項對應的Option物件:
Option createOption(String shortName, String longName, String argName, String description, boolean required) {
return Option.builder(shortName)
.longOpt(longName)
.argName(argName)
.desc(description)
.hasArg()
.required(required)
.build();
}
我們使用Option.Builder類別來設定 CLI 中輸入選項的短名稱、長名稱、參數名稱和描述。此外,在建構器類別中的required()方法的幫助下,我們認為先前定義的-U和-d選項是強制性的。
最後,我們將分別將帶有短名稱選項和長名稱選項的參數傳遞給方法parseThenProcessCommand() :
void parseThenProcessCommand(Options options, String[] commandArgs, String hostOption,
String usernameOption, String dbNameOption) throws ParseException {
CommandLineParser commandLineParser = new DefaultParser();
CommandLine commandLine = commandLineParser.parse(options, commandArgs);
String hostname = commandLine.hasOption("h") ? commandLine.getOptionValue(hostOption) : "localhost";
String username = commandLine.getOptionValue(usernameOption);
String dbName = commandLine.getOptionValue(dbNameOption);
if (commandLine.hasOption("h")) {
assertEquals("PGSERVER", hostname);
} else {
assertEquals("localhost", hostname);
}
assertEquals("postgres", userName);
assertEquals("empDB", dbName);
createConnection(hostname, username, dbName);
}
有趣的是,該方法可以處理具有短名稱和長選項名稱的命令。 CommandLineParser class解析參數,然後我們透過呼叫CommandLine物件的getOptionValue()方法來擷取它們的值。由於host是可選的,我們呼叫CommandLine類別中的hasOption()方法來偵測它是否存在。如果它不存在,我們將其值替換為預設的localhost.
最後,我們透過呼叫方法createConnection()將值傳遞給底層服務。
4.3.處理缺少的強制選項
在大多數 CLI 中,當缺少強制選項時,應顯示錯誤。假設psql指令中缺少強制host選項:
psql -h PGSERVER -U postgres
讓我們看看如何處理這個問題:
@Test
void whenMandatoryOptionMissing_thenThrowMissingOptionException() {
Options options = createOptions();
String[] commandWithMissingMandatoryOption = new String[]{"-h", "PGSERVER", "-U", "postgres"};
CommandLineParser commandLineParser = new DefaultParser();
assertThrows(MissingOptionException.class, () -> {
try {
CommandLine commandLine = commandLineParser.parse(options, commandWithMissingMandatoryOption);
} catch (ParseException e) {
assertTrue(e instanceof MissingOptionException);
handleException(new RuntimeException(e));
throw e;
}
});
}
當我們呼叫CommandLineParser類別中的parse()方法時,它會拋出MissingOptionException ,指示缺少所需的選項d 。接下來,我們呼叫方法handleException()來管理異常。
假設存在d選項,但缺少其參數:
psql -h PGSERVER -U postgres -d
現在,讓我們看看如何處理這個問題:
@Test
void whenOptionArgumentIsMissing_thenThrowMissingArgumentException() {
Options options = createOptions();
String[] commandWithOptionArgumentOption = new String[]{"-h", "PGSERVER", "-U", "postgres", "-d"};
CommandLineParser commandLineParser = new DefaultParser();
assertThrows(MissingArgumentException.class, () -> {
try {
CommandLine commandLine = commandLineParser.parse(options, commandWithOptionArgumentOption);
} catch (ParseException e) {
assertTrue(e instanceof MissingArgumentException);
handleException(new RuntimeException(e));
throw e;
}
});
}
當在CommandLineParser上呼叫parse()方法時,由於-d選項旁邊缺少參數,因此會引發MissingArgumentException 。再往下,我們呼叫handleException()來管理異常。
4.4.處理無法辨識的選項
有時,在執行命令時,我們會提供無法識別的選項:
psql -h PGSERVER -U postgres -d empDB -y
我們提供了一個不正確的、不存在的-y選項。我們來看看程式碼中是如何處理的:
@Test
void whenUnrecognizedOptionProvided_thenThrowUnrecognizedOptionException() {
Options options = createOptions();
String[] commandWithIncorrectOption = new String[]{"-h", "PGSERVER", "-U", "postgres", "-d", "empDB", "-y"};
CommandLineParser commandLineParser = new DefaultParser();
assertThrows(UnrecognizedOptionException.class, () -> {
try {
CommandLine commandLine = commandLineParser.parse(options, commandWithIncorrectOption);
} catch (ParseException e) {
assertTrue(e instanceof UnrecognizedOptionException);
handleException(new RuntimeException(e));
throw e;
}
});
}
parse()方法在遇到未知的-y選項時拋出UnrecogniedOptionException 。隨後,我們呼叫handleException()來管理執行階段異常。
4.5.處理互斥選項
考慮使用cp在 Unix 平台中複製檔案的命令:
cp -i -f file1 file2
-i選項在覆蓋檔案之前進行提示;但是, -f選項會覆蓋檔案而不提示。這兩個選項是相互衝突的,因此不應一起使用。
讓我們嘗試實作此驗證:
@Test
void whenMutuallyExclusiveOptionsProvidedTogether_thenThrowAlreadySelectedException() {
Option interactiveOption = new Option("i", false, "Prompts the user before overwriting the existing files");
Option forceOption = new Option("f", false, "Overwrites the existing files without prompting");
OptionGroup optionGroup = new OptionGroup();
optionGroup.addOption(interactiveOption)
.addOption(forceOption);
Options options = new Options();
options.addOptionGroup(optionGroup);
String[] commandWithConflictingOptions = new String[]{"cp", "-i", "-f", "file1", "file2"};
CommandLineParser commandLineParser = new DefaultParser();
assertThrows(AlreadySelectedException.class, () -> {
try {
CommandLine commandLine = commandLineParser.parse(options, commandWithConflictingOptions);
} catch (ParseException e) {
assertTrue(e instanceof AlreadySelectedException);
handleException(new RuntimeException(e));
throw e;
}
});
}
首先,我們使用其建構函式而不是Option.Builder類別來創建了相關的Option物件。這也是實例化Option類別的另一種方法。
OptionGroup類別有助於對互斥選項進行分組。因此,我們將這兩個選項加入OptionGroup物件中。然後,我們將OptionGroup物件新增至Options物件。最後,當我們呼叫CommandLineParser類別上的parse()方法時,它引發了AlreadySelectedException,表示有衝突的選項。
4.6.顯示幫助文字
格式化說明文字並將其顯示在終端上是所有 CLI 工具的共同關注點。因此,Apache Commons CLI 也在HelpFormatter類別的幫助下解決了這個問題。
讓我們以psql CLI 為例:
@Test
void whenNeedHelp_thenPrintHelp() {
HelpFormatter helpFormatter = new HelpFormatter();
Options options = createOptions();
options.addOption("?", "help", false, "Display help information");
helpFormatter.printHelp("psql -U username -h host -d empDB", options);
}
HelpFormatter類別中的printHelp()方法使用包含 CLI 定義的Options物件來顯示說明文字。此方法的第一個參數會產生頂部顯示的 CLI 的使用文字。
讓我們來看看HelpFormatter類別產生的輸出:
usage: psql -U username -h host -d empDB
-?,--help Display help information
-d,--dbName <DBNAME> Database name to connect to
-h,--host <HOST> Database server host
-U,--username <USERNAME> Database user name
5. 結論
在本文中,我們討論了 Apache Commons CLI 函式庫可幫助以標準化方式快速且有效率地建立 CLI 的能力。此外,該庫簡潔易懂。
值得注意的是,還有其他函式庫,如 JCommander、Airline 和 Picocli,它們同樣有效率且值得探索。與 Apache Commons CLI 不同,它們都支援註解。
與往常一樣,所使用的程式碼可以在 GitHub 上找到。