Apache Commons CLI 常用 CLI 命令与工具详解

Commons CLI 无自带 CLI,实测其工具链:javac/java -cp 直跑与 -D 位置坑、jar/javap 透视 jar、Maven 构建与多版本命令、wget/sha512sum/tar 发行包链,全部真实回显。

Apache Commons CLIApache Commons CLI2026/10/13

Apache Commons CLI 常用 CLI 命令与工具详解

目标:Commons CLI 是纯 Java 命令行解析库、不自带任何命令行可执行程序,本篇把「使用它时真正会用到的命令行工具链」一次讲透——javac/java 直跑与 JVM 参数位置、jar/javap 透视类库、Maven 构建与多版本命令、发行包下载校验解包链——已在 Ubuntu 22.04(OpenJDK 17.0.20.1、Maven 3.6.3、Commons CLI 1.11.0)实测通过。

一、环境信息

本篇所有命令与回显均在以下环境中实测采集,复现时请保持相近版本:

  • 操作系统:Ubuntu 22.04(jammy),x86_64
  • JDK:OpenJDK 17.0.20.1(apt 官方仓库 openjdk-17-jdk-headless)
  • 构建工具:Apache Maven 3.6.3(apt 官方仓库)
  • 组件版本:Apache Commons CLI 1.11.0(Maven 本地仓库 jar + /opt/cli-demo/lib 手动安装 jar 双份)
  • 实验目录:/opt/cli-demo(maven-demo/ 为 Maven 工程,javac-demo/ 为直跑示例,lib/ 与 downloads/ 为手动安装产物)
  • 执行用户:root
  • 运行约定:java 命令统一 java -Xmx256m -cp .:../lib/commons-cli-1.11.0.jar(在 javac-demo 目录内),下文简写为 $J;所有命令前 export LANG=C.UTF-8

二、工具概览

围绕 Commons CLI 的日常命令行工具链如下(全部为 JDK/系统自带命令,库本身不提供任何可执行程序):

工具来源用途本篇章节
javacJDK编译使用 Commons CLI 的示例源码四
javaJDK用 -cp 挂上 commons-cli jar 运行示例四
jarJDK查看包内条目、抽取 MANIFEST 核对版本五
javapJDK反编译查看类签名与常量,离线核对 API五
mvnMaven工程编译、运行、依赖解析与多版本管理六
wget系统包下载官方发行包与校验文件七
sha512sumcoreutils发行包完整性校验七
tar系统包解压发行包取出 jar七

💡 技术拓展:「命令行解析库」和「命令行工具」是两回事——

概念含义本篇对应
命令行解析库供你的 Java 程序 import 的 API(commons-cli.jar)全篇的主角,无任何 CLI 入口
命令行工具操作系统/JDK 提供的可执行命令javac/java/jar/javap/mvn 等

因此本篇的「常用 CLI 命令」指操作这个库时用到的系统工具链;解析库自身的参数体系见《Apache Commons CLI 配置参数详解》。

三、前置条件

确认 JDK、Maven 与两份 commons-cli jar 就位:

java -version 2>&1 | head -1 && mvn -v 2>/dev/null | head -1

回显(实测):

openjdk version "17.0.20.1" 2026-08-18
Apache Maven 3.6.3
ls /opt/cli-demo/lib/ /root/.m2/repository/commons-cli/commons-cli/1.11.0/*.jar

回显(实测):

/opt/cli-demo/lib/:
commons-cli-1.11.0.jar

/root/.m2/repository/commons-cli/commons-cli/1.11.0/:
commons-cli-1.11.0.jar

环境就位。以下所有命令都在 export LANG=C.UTF-8 前提下执行(locale 未设置时 javac 编译中文源码会失败,见第四节 4.2 与 FAQ Q1)。

四、javac 与 java -cp:不装构建工具直跑 Commons CLI

4.1 编译与运行一条链

以覆盖「布尔选项、取值、类型转换、裸参数」的 Demo1Basic 为例。先写入源码:

cd /opt/cli-demo/javac-demo && cat > Demo1Basic.java <<'EOF'
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public class Demo1Basic {
    public static void main(String[] args) {
        Options options = new Options();
        options.addOption("v", "verbose", false, "开启详细日志输出");
        options.addOption(Option.builder("n").longOpt("name").hasArg().argName("USER")
                .desc("指定运行用户名").get());
        options.addOption(Option.builder("c").longOpt("count").hasArg().argName("N")
                .type(Integer.class).desc("任务执行次数").get());

        try {
            CommandLine line = new DefaultParser().parse(options, args);
            if (line.hasOption("v")) {
                System.out.println("[1] -v/--verbose 命中,verbose = true");
            }
            if (line.hasOption("n")) {
                System.out.println("[2] -n/--name 命中,name = " + line.getOptionValue("name"));
            }
            if (line.hasOption("c")) {
                Integer count = (Integer) line.getParsedOptionValue("c");
                System.out.println("[3] -c/--count 命中,count = " + count
                        + "(自动转换为 " + count.getClass().getSimpleName() + ")");
            }
            System.out.println("[4] 未匹配任何选项的裸参数 = " + line.getArgList());
        } catch (ParseException e) {
            System.err.println("解析失败:" + e.getClass().getSimpleName() + " -> " + e.getMessage());
            System.exit(2);
        }
    }
}
EOF

回显(实测,无输出即成功):

编译加运行一条链完成:

cd /opt/cli-demo/javac-demo && export LANG=C.UTF-8 && javac -encoding UTF-8 -cp ../lib/commons-cli-1.11.0.jar Demo1Basic.java && java -Xmx256m -cp .:../lib/commons-cli-1.11.0.jar Demo1Basic -v --name tom --count 3 alpha beta

回显(实测):

[1] -v/--verbose 命中,verbose = true
[2] -n/--name 命中,name = tom
[3] -c/--count 命中,count = 3(自动转换为 Integer)
[4] 未匹配任何选项的裸参数 = [alpha, beta]

命令拆解:javac -encoding UTF-8 强制按 UTF-8 读源码;-cp ../lib/commons-cli-1.11.0.jar 把 commons-cli 挂进编译类路径;运行时 -cp .:../lib/... 中 . 是当前目录(找 Demo1Basic.class)、冒号分隔第二个条目(找 commons-cli 依赖),-Xmx256m 限制示例堆内存;-v --name tom --count 3 alpha beta 是被解析的程序参数。

再跑两个异常路径(类型错误与缺参数):

cd /opt/cli-demo/javac-demo && java -Xmx256m -cp .:../lib/commons-cli-1.11.0.jar Demo1Basic --count abc; echo "exit=$?"

回显(实测):

解析失败:ParseException -> java.lang.NumberFormatException: For input string: "abc"
exit=2
cd /opt/cli-demo/javac-demo && java -Xmx256m -cp .:../lib/commons-cli-1.11.0.jar Demo1Basic --name; echo "exit=$?"

回显(实测):

解析失败:MissingArgumentException -> Missing argument for option: n
exit=2

两条异常路径都以非零码 2 退出(示例代码在 ParseException 分支 System.exit(2)),脚本化调用时可用退出码判断成功与否。

4.2 编译坑:javac 默认编码不是 UTF-8

省略 -encoding UTF-8 编译含中文的源码时(locale 为 C 的环境),javac 按 US-ASCII 读源码直接编译失败。真实报错回显(实测):

Demo4Group.java:14: error: unmappable character (0xE8) for encoding US-ASCII
        group.addOption(Option.builder("j").longOpt("json").desc("?????? JSON ??????").build());
                                                                  ^

(同类错误累计 100 行,报错总览 100 errors。)修复即补 -encoding UTF-8(或 pom 的 project.build.sourceEncoding),详见 FAQ Q1。

4.3 JVM 参数位置:-D 必须放在主类名之前

验证类 Demo5Stop.java 通过 System.getProperty("stop", "false") 读取该属性决定 stopAtNonOption。先写入并编译:

cd /opt/cli-demo/javac-demo && cat > Demo5Stop.java <<'EOF'
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public class Demo5Stop {
    public static void main(String[] args) {
        boolean stopAtNonOption = Boolean.parseBoolean(System.getProperty("stop", "false"));
        Options options = new Options();
        options.addOption("v", "verbose", false, "开启详细日志输出");
        options.addOption(Option.builder("n").longOpt("name").hasArg().argName("USER")
                .desc("指定运行用户名").build());

        System.out.println("stopAtNonOption = " + stopAtNonOption);
        try {
            CommandLine line = new DefaultParser().parse(options, args, stopAtNonOption);
            System.out.println("verbose = " + line.hasOption("v"));
            System.out.println("name    = " + line.getOptionValue("name"));
            System.out.println("剩余裸参数 = " + line.getArgList());
        } catch (ParseException e) {
            System.err.println("解析失败:" + e.getMessage());
            System.exit(2);
        }
    }
}
EOF
export LANG=C.UTF-8 && javac -encoding UTF-8 -cp ../lib/commons-cli-1.11.0.jar Demo5Stop.java

回显(实测):

Note: Demo5Stop.java uses or overrides a deprecated API.
Note: Recompile with -Xlint:deprecation for details.

两条 Note 来自源码沿用的 Option.builder(...).build() 收尾写法——build() 在 1.11.0 已标记过时,编译照常成功、功能不受影响;新代码建议改用 .get() 收尾。-D 放错位置会静默失效甚至报错,放类名后(错误示范):

cd /opt/cli-demo/javac-demo && java -Xmx256m -cp .:../lib/commons-cli-1.11.0.jar Demo5Stop -Dstop=true -v -- alpha beta; echo "exit=$?"

回显(实测):

stopAtNonOption = false
解析失败:Unrecognized option: -Dstop=true
exit=2

放类名前(正确写法):

cd /opt/cli-demo/javac-demo && java -Xmx256m -Dstop=true -cp .:../lib/commons-cli-1.11.0.jar Demo5Stop -v alpha -n tom

回显(实测):

stopAtNonOption = true
verbose = true
name    = null
剩余裸参数 = [alpha, -n, tom]

对比鲜明:-D 在类名后时它变成了程序参数——stopAtNonOption 仍是默认 false,且 Demo5Stop 把 -Dstop=true 当选项解析报 Unrecognized option;挪到类名前才作为 JVM 系统属性生效(stopAtNonOption = true)。-Xmx、-cp 同理,全部 JVM 参数都必须在主类名之前。

4.4 -- 与裸参数:透传子命令

stopAtNonOption=true(或 -- 分隔符)配合 getArgList() 可实现子命令透传。Demo5Stop 的 stop=true 场景(上节回显)中,alpha 之后的 -n tom 不再被解析、原样进入裸参数 [alpha, -n, tom]——这是 mytool run -- java -jar app.jar 这类透传语义的实现基础。-- 分隔符单独实测:

cd /opt/cli-demo/javac-demo && java -Xmx256m -cp .:../lib/commons-cli-1.11.0.jar Demo5Stop -v -n tom -- alpha beta

回显(实测):

stopAtNonOption = false
verbose = true
name    = tom
剩余裸参数 = [alpha, beta]

-- 之后的内容全部视为裸参数(即便形如选项),与 stop=true 的区别是:-- 前的选项仍正常解析(name=tom),而 stop=true 遇到第一个裸参数就停止解析。

五、jar 与 javap:透视类库

5.1 jar tf:确认包内结构与类清单

cd /opt/cli-demo && jar tf lib/commons-cli-1.11.0.jar | wc -l

回显(实测):

73
cd /opt/cli-demo && jar tf lib/commons-cli-1.11.0.jar | grep -E '^org/apache/commons/cli/[A-Za-z]+\.class$' | sed 's|org/apache/commons/cli/||;s|\.class||' | sort

回显(实测):

AlreadySelectedException
AmbiguousOptionException
BasicParser
Char
CommandLine
CommandLineParser
Converter
DefaultParser
DeprecatedAttributes
GnuParser
HelpFormatter
MissingArgumentException
MissingOptionException
Option
OptionBuilder
OptionGroup
OptionValidator
Options
ParseException
Parser
PatternOptionBuilder
PosixParser
TypeHandler
UnrecognizedOptionException
Util

25 个顶层类构成三层结构:核心(Options/Option/CommandLine/DefaultParser)、异常族(ParseException 及五个子类)、兼容层(Parser/BasicParser/GnuParser/PosixParser/OptionBuilder 为 1.x 早期 API,新代码不要用)。jar 内还有 org/apache/commons/cli/help/ 子包(新版帮助格式化器):

cd /opt/cli-demo && jar tf lib/commons-cli-1.11.0.jar | grep "help/" | head -8

回显(实测):

org/apache/commons/cli/help/
org/apache/commons/cli/help/AbstractHelpFormatter$Builder.class
org/apache/commons/cli/help/AbstractHelpFormatter.class
org/apache/commons/cli/help/FilterHelpAppendable.class
org/apache/commons/cli/help/HelpAppendable.class
org/apache/commons/cli/help/HelpFormatter$Builder.class
org/apache/commons/cli/help/HelpFormatter.class
org/apache/commons/cli/help/OptionFormatter$1.class

5.2 jar xf:核对运行时版本

排查版本冲突时从实际加载的 jar 里抽出 MANIFEST 核对:

cd /tmp && jar xf /opt/cli-demo/lib/commons-cli-1.11.0.jar META-INF/MANIFEST.MF && cat META-INF/MANIFEST.MF

输出节选(实测):

Manifest-Version: 1.0
Created-By: Apache Maven Bundle Plugin 6.0.0
Build-Jdk-Spec: 25
Implementation-Title: Apache Commons CLI
Implementation-Vendor: The Apache Software Foundation
Implementation-Version: 1.11.0
Specification-Title: Apache Commons CLI
Specification-Version: 1.11.0
X-Compile-Source-JDK: 1.8
X-Compile-Target-JDK: 1.8
Multi-Release: true

关键字段:Implementation-Version: 1.11.0 确认版本;X-Compile-Target-JDK: 1.8 说明字节码目标 Java 8(任何 Java 8+ 运行时可用);Multi-Release: true 表示这是多版本 jar(对高版本 JDK 有针对性实现)。

5.3 javap:离线核对 API 签名与常量

不确定某方法是否存在时,javap 反编译比查在线文档快(离线服务器尤其如此)。查核心类签名:

javap -cp /opt/cli-demo/lib/commons-cli-1.11.0.jar org.apache.commons.cli.CommandLineParser

回显(实测):

Compiled from "CommandLineParser.java"
public interface org.apache.commons.cli.CommandLineParser {
  public abstract org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.lang.String[]) throws org.apache.commons.cli.ParseException;
  public abstract org.apache.commons.cli.CommandLine parse(org.apache.commons.cli.Options, java.lang.String[], boolean) throws org.apache.commons.cli.ParseException;
}

查版式常量的真实值(-constants 输出 final 常量的值):

javap -constants -cp /opt/cli-demo/lib/commons-cli-1.11.0.jar org.apache.commons.cli.HelpFormatter | grep "DEFAULT_"

回显(实测):

  public static final int DEFAULT_WIDTH = 74;
  public static final int DEFAULT_LEFT_PAD = 1;
  public static final int DEFAULT_DESC_PAD = 3;
  public static final java.lang.String DEFAULT_SYNTAX_PREFIX = "usage: ";
  public static final java.lang.String DEFAULT_OPT_PREFIX = "-";
  public static final java.lang.String DEFAULT_LONG_OPT_PREFIX = "--";
  public static final java.lang.String DEFAULT_LONG_OPT_SEPARATOR = " ";
  public static final java.lang.String DEFAULT_ARG_NAME = "arg";

本实验初版代码里凭旧记忆写的 OptionGroup.setName、HelpFormatter.setRightPadding、DefaultParser.setAllowPartialMatching 都被 javap 证伪(cannot find symbol 编译错误),方法存在性以 javap 输出为准。

六、Maven 工具链命令

6.1 工程编译:mvn clean compile

本节命令在 /opt/cli-demo/maven-demo 的 Maven 工程上执行,先创建工程并写入 pom 与主类:

mkdir -p /opt/cli-demo/maven-demo/src/main/java/com/example
cat > /opt/cli-demo/maven-demo/pom.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>maven-cli-demo</artifactId>
  <version>1.0.0</version>
  <packaging>jar</packaging>

  <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <dependency>
      <groupId>commons-cli</groupId>
      <artifactId>commons-cli</artifactId>
      <version>1.11.0</version>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>3.1.0</version>
        <configuration>
          <mainClass>com.example.Main</mainClass>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>
EOF
cat > /opt/cli-demo/maven-demo/src/main/java/com/example/Main.java <<'EOF'
package com.example;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public class Main {
    public static void main(String[] args) {
        Options options = new Options();
        options.addOption("v", "verbose", false, "开启详细日志输出");
        options.addOption(Option.builder("n").longOpt("name").hasArg().argName("USER")
                .desc("指定运行用户名").get());
        options.addOption(Option.builder("c").longOpt("count").hasArg().argName("N")
                .type(Integer.class).desc("任务执行次数").get());

        try {
            if (args.length == 0) {
                new HelpFormatter().printHelp("maven-cli-demo [选项] [参数]", options, true);
                return;
            }
            CommandLine line = new DefaultParser().parse(options, args);
            if (line.hasOption("v")) {
                System.out.println("verbose = true");
            }
            if (line.hasOption("n")) {
                System.out.println("name = " + line.getOptionValue("n"));
            }
            if (line.hasOption("c")) {
                Integer count = (Integer) line.getParsedOptionValue("c");
                System.out.println("count = " + count + "(Integer)");
            }
        } catch (ParseException e) {
            System.err.println("解析失败:" + e.getMessage());
            System.exit(2);
        }
    }
}
EOF

回显(实测,无输出即成功):

pom 逐项说明:groupId/artifactId/version 标识示例工程(com.example:maven-cli-demo:1.0.0);maven.compiler.source/target 设为 8,对齐 Commons CLI 1.11.0 的最低 Java 8 要求;project.build.sourceEncoding 固定 UTF-8,保证中文源码正常编译;dependencies 引入 commons-cli:commons-cli:1.11.0(无传递依赖,单 jar 即可用);exec-maven-plugin 的 mainClass 指向 com.example.Main,供 6.2 节的 mvn exec:java 直接运行主类。

主类 Main.java 覆盖「无参数打印帮助、解析取值、类型转换」闭环。编译时 Maven 会给出两条 [WARNING](Main.java uses or overrides a deprecated API.),来自示例中的 new HelpFormatter()——该类在 1.11.0 已标记过时、功能不受影响,新代码可用 HelpFormatter.builder().get()。

pom 与主类就位后执行工程编译:

cd /opt/cli-demo/maven-demo && mvn clean compile 2>&1 | grep -E "compiler-plugin.*compile|BUILD|Total time"

回显(实测):

[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ maven-cli-demo ---
[INFO] BUILD SUCCESS
[INFO] Total time:  1.114 s

首次编译会先连续下载各构建插件与 commons-cli 依赖(多行 Downloading/Downloaded from central: ...),此后增量编译走本地仓库缓存,输出只剩编译与 BUILD SUCCESS 两段。

6.2 运行:exec:java 与 classpath 直跑两种姿势

cd /opt/cli-demo/maven-demo && mvn -q compile exec:java -Dexec.args="--name tom --count 5"

回显(实测):

name = tom
count = 5(Integer)
cd /opt/cli-demo/maven-demo && java -Xmx256m -cp target/classes:$HOME/.m2/repository/commons-cli/commons-cli/1.11.0/commons-cli-1.11.0.jar com.example.Main --name tom --count 5

回显(实测):

name = tom
count = 5(Integer)

两种运行方式结果一致。注意 exec:java 走 Maven 内嵌类路径(不产生独立 java 进程参数),classpath 直跑则完全可控,排查类路径问题时优先用直跑。-Dexec.args 里的参数整体作为一个字符串传给 exec 插件再拆分,含空格的参数值要用引号包好。

6.3 依赖核对:dependency:list 与 dependency:get

核对工程实际解析到的 commons-cli 版本:

cd /opt/cli-demo/maven-demo && mvn -B dependency:list | grep commons-cli

回显(实测):

[INFO]    commons-cli:commons-cli:jar:1.11.0:compile

不建工程、按坐标直接拉任意版本进本地仓库:

cd /opt/cli-demo/maven-demo && mvn -q dependency:get -Dartifact=commons-cli:commons-cli:1.10.0 && ls $HOME/.m2/repository/commons-cli/commons-cli/

回显(实测):

1.0
1.10.0
1.11.0

本地仓库按版本目录天然共存;1.0 目录是解析 POM 时被引用拉取的旧坐标缓存(1.11.0 的 POM 内部引用了 1.0 的坐标),不影响运行。降级只需改 pom 的 version 后重新 mvn compile。

6.4 清理:mvn clean 与本地仓库清理

cd /opt/cli-demo/maven-demo && mvn clean 2>&1 | tail -3 && ls target 2>&1

回显(实测):

[INFO] Total time:  0.204 s
[INFO] Finished at: 2026-10-13T06:59:25Z
[INFO] ------------------------------------------------------------------------
ls: cannot access 'target': No such file or directory

mvn clean 删除 target/(含已编译 class),ls target 报 No such file or directory 即清理完成;要连本地仓库缓存一起清则 rm -rf $HOME/.m2/repository/commons-cli。

七、发行包工具链:wget / sha512sum / tar

7.1 下载与校验

mkdir -p /opt/cli-demo/downloads && cd /opt/cli-demo/downloads && wget -q https://downloads.apache.org/commons/cli/binaries/commons-cli-1.11.0-bin.tar.gz && wget -q https://downloads.apache.org/commons/cli/binaries/commons-cli-1.11.0-bin.tar.gz.sha512 && printf '%s  %s\n' "$(cat commons-cli-1.11.0-bin.tar.gz.sha512)" commons-cli-1.11.0-bin.tar.gz | sha512sum -c -

回显(实测):

commons-cli-1.11.0-bin.tar.gz: OK

.sha512 文件为裸哈希,用 printf '%s %s\n' 拼成「哈希 文件名」标准格式再交给 sha512sum -c - 自动比对;若报 FAILED,直接重新下载并复验,通过后再解包使用。

7.2 解包取 jar

cd /opt/cli-demo/downloads && tar xzf commons-cli-1.11.0-bin.tar.gz && ls commons-cli-1.11.0/ && cp commons-cli-1.11.0/commons-cli-1.11.0.jar ../lib/

回显(实测,目录内容):

CONTRIBUTING.md
LICENSE.txt
NOTICE.txt
README.md
RELEASE-NOTES.txt
apidocs
commons-cli-1.11.0-javadoc.jar
commons-cli-1.11.0-sources.jar
commons-cli-1.11.0-test-sources.jar
commons-cli-1.11.0-tests.jar
commons-cli-1.11.0.jar

主 jar 复制到 lib/ 后即可供第四节直跑命令使用;apidocs/ 是离线 javadoc,内网环境可直接浏览器打开阅读。

八、注意事项

  1. JVM 参数全部在主类名之前:-cp、-Xmx256m、-D系统属性 放到主类名之后会被当作程序参数,-D 失效且常引发 Unrecognized option(4.3 节实测)。
  2. javac 一律带 -encoding UTF-8:locale 为 C 的机器上默认 US-ASCII,中文源码直接编译失败(4.2 节实测)。
  3. 退出码约定:示例程序解析失败 System.exit(2),脚本化调用用 $? 判断(4.1 节实测 exit=2);java 自身找不到主类也是非零(exit=1)。
  4. -cp 分隔符是冒号:Linux 上用 :(Windows 用 ;),多条目时当前目录 . 别漏(漏掉会 ClassNotFoundException 找不到自己的类)。
  5. 新代码避开兼容层:Parser/BasicParser/GnuParser/PosixParser/OptionBuilder(5.1 节类清单中标出的兼容层)在 1.x 早期即被 DefaultParser 取代,且 OptionBuilder 是静态可变状态、并发不安全。
  6. mvn -q 只在确认无误后用:静默模式会吞掉依赖下载与告警信息,排查问题时去掉 -q 看全量 [INFO]/[WARNING] 输出。

九、常见问题(FAQ)

Q1:javac 报 error: unmappable character (0xE8) for encoding US-ASCII 怎么办? locale 未设置为 UTF-8 时 javac 退回 US-ASCII 读源码,中文字符全部报 unmappable character(真实回显见 4.2 节)。双保险修复:命令前 export LANG=C.UTF-8 + javac 带 -encoding UTF-8;Maven 工程则确认 pom 含 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>。

Q2:运行报 ClassNotFoundException: Demo1Basic(或找不到依赖类),怎么排查? 两步:第一,确认 -cp 里 .(当前目录)没漏、且在源码所在目录运行;第二,确认 commons-cli jar 路径真实存在(ls 那个 jar 路径)。实测中把 JVM 参数写到类名后还会触发另一个表象——-cp 随之失效,报 Error: Unable to initialize main class Demo5Stop / Caused by: java.lang.NoClassDefFoundError: org/apache/commons/cli/ParseException(主类能从默认类路径加载、依赖类加载不到),牢记 JVM 参数全部在主类名之前(4.3 节)。

Q3:java -cp 与 mvn exec:java 跑同一程序输出不一致? 先核对两者加载的 jar 版本是否一致:classpath 直跑用 -cp 里手写的 jar,exec:java 用工程 pom 解析结果。分别用 5.2 节的 jar xf ... MANIFEST.MF 与 6.3 节的 mvn dependency:list 核对,-cp 中只保留一个版本即可对齐。

Q4:sha512sum -c 报 FAILED 但文件大小和官方一致? 大小一致不代表内容一致(代理改写、传输截断后补零都可能保大小)。实测在下载文件尾部追加一个字节再校验,即报 FAILED。直接重新下载,并用 printf ... | sha512sum -c - 校验通过后再解包使用。

Q5:mvn dependency:get 拉了 1.10.0,工程里为什么还在用 1.11.0? dependency:get 只把指定版本下载进本地仓库,不改变任何工程的依赖声明;工程实际版本由 pom 的 <version> 决定,用 mvn dependency:list 核对(6.3 节实测仍为 1.11.0:compile)。切换版本要改 pom 并重新编译。

十、参考链接

  1. Apache Commons CLI 项目主页:https://commons.apache.org/proper/commons-cli/
  2. Commons CLI 1.11.0 javadoc(在线):https://commons.apache.org/proper/commons-cli/javadocs/api-release/
  3. JDK jar / javap / java 工具参考:https://docs.oracle.com/en/java/javase/17/docs/specs/man/
  4. Apache Maven 官方文档:https://maven.apache.org/guides/index.html
  5. Apache Commons CLI 官方下载:https://downloads.apache.org/commons/cli/