Apache Jelly 常用 CLI 命令与工具详解
逐参数实测 Apache Jelly 命令行(jelly 脚本/-script/-o/-D/-awt/-swing/--help 及退出码行为)与 Ant Task 方式调用(JellyTask taskdef),并附 jelly 与 Ant 双向集成的完整命令与回显。
Apache Jelly 常用 CLI 命令与工具详解
目标:把 Apache Commons Jelly 1.0.1 自带的命令行工具
jelly的每个参数逐个实测(含真实回显与退出码),并完整演示「Ant Task 方式调用 Jelly 脚本」与「Jelly 脚本内调用 Ant 任务」的双向集成。全部命令均在 Ubuntu 22.04 + OpenJDK 17 环境实测,示例目录/root/jelly-demo/。
一、环境信息
- 操作系统:Ubuntu 22.04(x86_64)
- JDK:OpenJDK 17.0.20.1(实测可正常运行 Jelly 1.0.1)
- 实测对象:commons-jelly 1.0.1(Maven Central 坐标
commons-jelly:commons-jelly) - Ant:Apache Ant(TM) version 1.10.12(
apt仓库ant) - 运行环境:
/opt/jelly/lib/(依赖 jar),启动脚本/usr/local/bin/jelly - 示例目录:
/root/jelly-demo/
环境核实回显:
java -version 2>&1 | head -1
openjdk version "17.0.20.1" 2026-08-18
ant -version
Apache Ant(TM) version 1.10.12 compiled on January 17 1970
💡 技术拓展:Java 命令行工具的参数解析库常见三种——
解析库 特点 典型用户 Apache Commons CLI 老牌,POSIX/GNU 风格选项 jelly 1.0.1(本次实测) JCommander 注解式声明,现代 各类新工具 picocli 注解 + 子命令 + 彩色帮助 新一代 CLI jelly 1.0.1 采用 Commons CLI 1.0 的 GnuParser,因此本文所有参数行为均可从
CommandLineParser源码与其真实回显双向印证。
二、工具概览
Jelly 的「工具面」由两部分组成(均在实测 jar 中确认存在):
| 工具 | 入口类 | 方向 | 用途 |
|---|---|---|---|
jelly 命令行 | org.apache.commons.jelly.Jelly#main | 终端 → Jelly 脚本 | 独立运行 .jelly 脚本 |
JellyTask(Ant Task) | org.apache.commons.jelly.task.JellyTask | Ant → Jelly | 在 build.xml 中把 Jelly 脚本作为构建步骤 |
AntTagLibrary(jelly:ant 标签库) | org.apache.commons.jelly.tags.ant.AntTagLibrary | Jelly → Ant | 在 .jelly 脚本中直接使用 Ant 任务 |
三者的依赖构件:jelly 只需主 jar 与基础依赖;JellyTask/AntTagLibrary 额外需要 commons-jelly-tags-ant-20040902.071105.jar、commons-grant-1.0-beta-4.jar 及 ant 运行时(ant.jar、ant-launcher.jar、ant-junit.jar、ant-junit4.jar——实测 ant-junit 缺失时抛 NoClassDefFoundError: org/apache/tools/ant/taskdefs/optional/junit/FormatterElement$TypeAttribute)。
三、前置条件
沿用《Apache Jelly 在 Ubuntu 22.04 集成使用指南》组装好的环境(/opt/jelly/lib + /usr/local/bin/jelly)。本文额外安装 Ant 并把 ant 相关 jar 补进 classpath 目录:
apt-get install -y ant
cp /usr/share/ant/lib/ant.jar /usr/share/ant/lib/ant-launcher.jar /opt/jelly/lib/
cp /usr/share/ant/lib/ant-junit.jar /usr/share/ant/lib/ant-junit4.jar /opt/jelly/lib/
curl -fsSL -o /opt/jelly/lib/commons-jelly-tags-ant-20040902.071105.jar \
https://repo1.maven.org/maven2/commons-jelly/commons-jelly-tags-ant/20040902.071105/commons-jelly-tags-ant-20040902.071105.jar
curl -fsSL -o /opt/jelly/lib/commons-grant-1.0-beta-4.jar \
https://repo1.maven.org/maven2/commons-grant/commons-grant/1.0-beta-4/commons-grant-1.0-beta-4.jar
验证 Ant 与 jelly 就绪:
ant -version && jelly --help | head -2
Apache Ant(TM) version 1.10.12 compiled on January 17 1970
usage: jelly [scriptFile] [-script scriptFile] [-o outputFile]
四、jelly 命令详解(逐参数实测)
4.1 无参数:短用法提示
cd /root/jelly-demo && jelly
Usage: jelly [scriptFile] [-script scriptFile -o outputFile -Dsysprop=syspropval]
退出码 0。该行提示来自 Jelly.main(源码核实:args.length <= 0 时打印后直接 return)。
4.2 --help / -h:完整用法
jelly --help
usage: jelly [scriptFile] [-script scriptFile] [-o outputFile]
[-Dsysprop=syspropval] [-awt]
-swing Synonym of "-awt".
-awt Wether to run in the AWT thread.
-h,--help Give this help message
-o Output file
-script Jelly script to run
-v,--version prints Jelly's version and exits
-h 与 --help 等价,实测两者退出码均为 1(源码中打印用法后 System.exit(1))。
4.3 -v / --version:实测崩溃(打包缺陷)
jelly -v
java.lang.NullPointerException
at java.base/java.io.Reader.<init>(Reader.java:168)
at java.base/java.io.InputStreamReader.<init>(InputStreamReader.java:97)
at org.apache.commons.jelly.Jelly.readBuildTimestampResource(Jelly.java:119)
at org.apache.commons.jelly.Jelly.getJellyVersion(Jelly.java:108)
at org.apache.commons.jelly.util.CommandLineParser.invokeCommandLineJelly(CommandLineParser.java:82)
at org.apache.commons.jelly.Jelly.main(Jelly.java:93)
Exception in thread "main" java.lang.IllegalStateException: Resource "jelly-version.txt" not found.
原因(源码 + jar 解包双重核实):Jelly.getJellyVersion() 读取 jar 内资源 jelly-version.txt,而 Central 打包的 1.0.1 jar 只有 org/apache/commons/jelly/jelly.properties、没有该资源文件。这是发布缺陷,任何正常安装都无法用 -v 查版本;版本以 Maven Central 元数据为准(1.0.1)。退出码 1。
4.4 位置参数运行脚本
cd /root/jelly-demo && jelly hello.jelly
Hello, Jelly!
Hello, Ubuntu 22.04!
退出码 0。注意引擎只认 第一个 位置参数作为脚本(源码:scriptFile = args[0]),多给的文件不会被执行。
4.5 -script:显式指定脚本
jelly -script hello.jelly
Hello, Jelly!
Hello, Ubuntu 22.04!
与位置参数等价。当 -script 存在时优先于 args[0]。
4.6 -o:输出重定向到文件
cd /root/jelly-demo && jelly hello.jelly -o out.txt
echo EXIT=$?
cat out.txt
EXIT=0
Hello, Jelly!
Hello, Ubuntu 22.04!
脚本正常输出不再打印到终端,而是写入 out.txt(底层是 XMLOutput.createXMLOutput(new FileWriter(...)))。
⚠️ 注意:
-o放在脚本名 前面 会踩 args[0] 陷阱——实测jelly -o out.txt hello.jelly直接报错:org.apache.commons.jelly.JellyException: null:-1:-1: <null> Script file -o not found原因:位置参数从原始 args 数组取下标 0,
-o被当成了脚本名。保险写法:脚本名放最前,或一律用-script指定脚本。
4.7 -D:注入系统属性(脚本内以变量读取)
cat > /root/jelly-demo/sysprop.jelly <<'EOF'
<?xml version="1.0"?>
<j:jelly xmlns:j="jelly:core">
env from -D: ${env} / host: ${hostname}
</j:jelly>
EOF
cd /root/jelly-demo && jelly sysprop.jelly -Denv=production -Dhostname=web01
env from -D: production / host: web01
-Dk=v 由解析器直接写入 JVM 系统属性,引擎运行时把系统属性并入脚本上下文,因此脚本里 ${env}、${hostname} 能直接取到。
位置同样有讲究(实测 jelly -Denv=production sysprop.jelly):
org.apache.commons.jelly.JellyException: null:-1:-1: <null> Script file -Denv=production not found
同样是 args[0] 陷阱——-D 选项要放在脚本名之后。
非法 -D 的真实行为(缺 =,实测):
jelly sysprop.jelly -Dbroken
Invalid system property: "broken".
Exception in thread "main" java.lang.StringIndexOutOfBoundsException: begin 0, end -1, length 6
at java.base/java.lang.String.substring(String.java:2709)
at org.apache.commons.jelly.util.CommandLineParser.parseCommandLineOptions(CommandLineParser.java:187)
先打印 Invalid system property,随后解析器自身缺陷(对 indexOf('=')==-1 未短路)导致 StringIndexOutOfBoundsException,退出码 1。
4.8 -awt / -swing:在 AWT(Swing) 线程中执行
两个参数互为同义词(源码:cmdLine.hasOption("awt") || cmdLine.hasOption("swing")),脚本体在 Swing 事件线程中执行(GUI 类标签库需要)。无显示环境的服务器上执行普通脚本实测正常:
cd /root/jelly-demo && jelly showprops.jelly -awt
who = from-jelly.properties / mode = demo
同样受 args[0] 陷阱约束(jelly -awt showprops.jelly 实测报 Script file -awt not found)。
4.9 脚本内可用的内置变量
解析器会把两组运行时信息注入脚本上下文(源码核实 + 实测):
cat > /root/jelly-demo/vars.jelly <<'EOF'
<?xml version="1.0"?>
<j:jelly xmlns:j="jelly:core">
args[0] = ${args[0]}
args[1] = ${args[1]}
commandLine.hasOption("script") = ${commandLine.hasOption("script")}
</j:jelly>
EOF
cd /root/jelly-demo && jelly vars.jelly A1 A2
args[0] = vars.jelly
args[1] = A1
commandLine.hasOption("script") = false
args:原始命令行数组(下标 0 是脚本名本身,业务参数从 1 起);commandLine:Commons CLI 的CommandLine对象,可在脚本里调用其方法判断选项。
4.10 脚本解析顺序与 jelly.properties 自动注入
jelly 按以下顺序定位脚本(源码核实):classpath 资源 → 文件路径 → URL。变量来源优先级上,jelly.properties(用户主目录 → 当前目录 → classpath)会在编译脚本前注入根上下文,实测:当前目录放 jelly.properties(内容 who=from-jelly.properties、mode=demo)后运行 ${who}/${mode} 输出:
who = from-jelly.properties / mode = demo
4.11 退出码行为(自动化运维必读)
Jelly.main 捕获 JellyException 后只打印堆栈并正常返回(源码核实),因此脚本缺失、解析失败这类错误退出码仍是 0,实测:
cd /root/jelly-demo && jelly no-such.jelly; echo EXIT=$?
org.apache.commons.jelly.JellyException: null:-1:-1: <null> Script file no-such.jelly not found
EXIT=0
| 场景 | 退出码(实测) |
|---|---|
| 正常运行 | 0 |
| 脚本不存在 / 表达式解析失败 | 0(错误只体现在 stderr) |
-h/--help | 1 |
-v(崩溃) | 1 |
非法 -D(解析器抛未捕获异常) | 1 |
⚠️ 注意:用 shell 判断 Jelly 是否失败不能只看退出码,须同时检查 stderr(如
jelly a.jelly 2> err.log || true后再校验 err.log)。
五、Ant Task 方式调用 Jelly(Ant → Jelly)
5.1 JellyTask 的属性
org.apache.commons.jelly.task.JellyTask 继承 org.apache.tools.ant.Task,实测 javap 摘录其属性 setter:
public void setScript(java.lang.String) throws java.net.MalformedURLException;
public void setUrl(java.net.URL);
public void setFile(java.io.File) throws java.net.MalformedURLException;
public void setOutput(java.io.File) throws java.io.IOException;
即 build.xml 中可用属性:script、url、file、output(四选一给脚本位置,output 可选)。
5.2 build.xml 与被调用的 Jelly 脚本
Ant 侧 /root/jelly-demo/build.xml:
<?xml version="1.0"?>
<project name="jelly-demo" default="run" basedir=".">
<target name="run">
<taskdef name="jelly" classname="org.apache.commons.jelly.task.JellyTask">
<classpath>
<fileset dir="/opt/jelly/lib" includes="*.jar"/>
</classpath>
</taskdef>
<jelly file="/root/jelly-demo/ant-hook.jelly"/>
</target>
</project>
被调用的 /root/jelly-demo/ant-hook.jelly(演示 Ant Project 自动注入为 project 变量):
<?xml version="1.0"?>
<j:jelly xmlns:j="jelly:core">
hello from jelly running inside Ant
project.name = ${project.name} default = ${project.defaultTarget}
</j:jelly>
5.3 实测运行
cd /root/jelly-demo && ant -f build.xml
Buildfile: /root/jelly-demo/build.xml
run:
[jelly] Running script: file:/root/jelly-demo/ant-hook.jelly
[jelly] hello from jelly running inside Ant
[jelly] project.name = jelly-demo default = run
BUILD SUCCESSFUL
Total time: 0 seconds
要点:Jelly 的每行输出被 Ant 日志加上 [jelly] 前缀;JellyTask 把 Ant Project 对象注入上下文变量 project,脚本里 ${project.name}、${project.defaultTarget} 实测取到 jelly-demo、run。
六、Jelly 脚本内调用 Ant 任务(Jelly → Ant)
6.1 脚本
/root/jelly-demo/jelly-ant.jelly:
<?xml version="1.0"?>
<j:jelly xmlns:j="jelly:core" xmlns:ant="jelly:ant">
<ant:mkdir dir="/root/jelly-demo/ant-made"/>
<ant:echo message="echo from ant task inside jelly"/>
<j:forEach var="i" begin="1" end="3">
<ant:echo message="loop ${i}: task via ant"/>
</j:forEach>
</j:jelly>
6.2 实测运行
cd /root/jelly-demo && jelly jelly-ant.jelly
ls -d /root/jelly-demo/ant-made
[mkdir] Created dir: /root/jelly-demo/ant-made
[echo] echo from ant task inside jelly
[echo] loop 1: task via ant
[echo] loop 2: task via ant
[echo] loop 3: task via ant
/root/jelly-demo/ant-made
Ant 任务的日志格式([mkdir]/[echo])与 Ant 原生一致,目录真实创建。依赖排障实录(均在实测中复现,按三、前置补齐即可):缺 commons-jelly-tags-ant 时引擎打 SEVERE: Could not find the class: org.apache.commons.jelly.tags.ant.AntTagLibrary 并抛 ClassNotFoundException;缺 ant jar 时报 NoClassDefFoundError: org/apache/tools/ant/BuildLogger;缺 ant-junit 时报 Exception in thread "main" java.lang.NoClassDefFoundError: org/apache/tools/ant/taskdefs/optional/junit/FormatterElement$TypeAttribute。
七、注意事项
- args[0] 陷阱贯穿所有选项:
-o、-D、-awt、-swing一律写在脚本名之后,或用-script显式指定脚本(四、6 实测复现)。 - 一个命令只跑一个脚本:多个位置参数时只有 args[0] 生效;要跑多个脚本就调用多次或在一个脚本里
include。 - -v 不可用:Central 1.0.1 jar 缺版本资源,
jelly -v必崩(四、3),版本核对走 Maven Central 元数据。 - 退出码不可靠:脚本级失败退出码为 0,自动化须检查 stderr(四、11)。
- Ant 集成的 classpath 要含 ant-junit:
AntTagLibrary静态初始化引用FormatterElement,缺了直接类加载失败(六、2)。
八、常见问题
Q1:jelly hello.jelly 提示 Script file not found,但文件确实在?
检查三点(均实测定位):① 是否把 -D/-o 等选项放在了脚本名前(args[0] 被选项抢占);② 相对路径是否对应当前工作目录(引擎按 user.dir 解析相对文件);③ 脚本名是否拼错——报错信息会原样带出被当作脚本的字符串,如 Script file -o not found。
Q2:如何在脚本里取到命令行参数?
用内置变量 args(数组,args[0] 是脚本名)与 commandLine(Commons CLI 对象,可调用 hasOption() 等方法),实测见四、9。
Q3:能否把 -D 传进来的属性在脚本里改写?
可以。-D 写的是 JVM 系统属性;脚本里用 <j:set var="env" value="staging"/> 覆盖的是上下文变量,两者独立。若要改系统属性本身,用 j:invokeStatic 调 System.setProperty(core 标签支持,见《Apache Jelly 在 Ubuntu 22.04 集成使用指南》六、2)。
Q4:JellyTask 执行失败时 Ant 构建会失败吗?
会。JellyTask.execute() 把脚本异常包装成 org.apache.tools.ant.BuildException 抛出(javap 字节码可见 new BuildException),Ant 将以 BUILD FAILED 终止并回显脚本错误位置。
九、参考链接
- jelly 官网 Getting Started / Tutorial:https://commons.apache.org/proper/commons-jelly/
- commons-jelly-tags-ant 构件:https://repo1.maven.org/maven2/commons-jelly/commons-jelly-tags-ant/
- 集成环境搭建:《Apache Jelly 在 Ubuntu 22.04 集成使用指南》
- 选型与停滞现状:《Apache Jelly 介绍与选型指南》