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

逐类讲解 Apache Commons Exec 1.6.0 核心类与调用形态:CommandLine 解析与变量替换、DefaultExecutor 执行、流处理、超时看门狗、异步回调、OS 判定,附自写执行工具实测回显。

Apache Commons ExecApache Commons Exec2026/10/14

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

目标:把 Apache Commons Exec 1.6.0 的核心类当成一套「命令执行工具」逐类讲清其调用形态与真实运行结果——从 CommandLine 构造、DefaultExecutor 执行,到流处理、超时看门狗、异步回调与自定义流处理器。所有命令与回显已在 Ubuntu 22.04 + JDK 17 上实测通过。

Apache Commons Exec 本身是 Java 库,没有独立的命令行程序;所谓「CLI 与工具」,指的是「用 javac/java -cp 编译运行、并按核心类拼出执行外部命令的工具代码」这一整套用法。本文先把核心类职责排开,再逐类给出可编译运行的调用形态与实测输出,最后给出一个自写的通用执行工具。

💡 技术拓展:在 JVM 里执行外部命令,业界有多种方案,选型前先看清各自边界。

方案所属特点适用场景
Runtime.exec()JDK 内置最简单,无超时、无流处理,易造成管道缓冲区死锁一次性简单调用
ProcessBuilderJDK 内置支持重定向与合并输出流,但超时/异步仍需自行实现可控的中等复杂度调用
JNA / JNI第三方 / 原生直接调用系统 API,能力最强但需平台库,部署重需操作系统级控制的场景
Apache Commons Exec独立库统一封装执行、流泵、超时看门狗、异步结果处理,零传递依赖需要超时控制与输出捕获的工程

本文所有示例都基于 Apache Commons Exec 1.6.0,它把「执行 + 超时 + 输出捕获 + 异步回调」封装成一组可组合对象;下文逐个说明这些对象。

一、环境信息

本章确认实测环境与库版本。Commons Exec 1.6.0 的字节码目标是 Java 8,用 JDK 17 编译运行完全兼容。

确认系统与 Java 版本:

cat /etc/os-release | grep -E "^VERSION="
java -version 2>&1 | head -1

实测回显:

VERSION="22.04.5 LTS (Jammy Jellyfish)"
openjdk version "17.0.20.1" 2026-08-18

二、工具概览

本章先给出核心类清单与职责,明确「哪个类干哪件事」,后续章节逐类展开调用形态。Commons Exec 的常用类共 8 个,按职责可分为「命令描述」「执行主体」「流处理」「超时与结果」四组。

类分组核心职责常用入口
CommandLine命令描述描述可执行文件与参数,支持引号与 ${GREETING} 这类变量替换new CommandLine(...)、CommandLine.parse(...)、addArgument(...)
DefaultExecutor执行主体执行命令,承载工作目录、退出值判定、看门狗DefaultExecutor.builder().get()、execute(...)
PumpStreamHandler流处理把子进程 stdout/stderr 泵到输出流,把输入写入 stdinnew PumpStreamHandler(out, err)
ExecuteStreamHandler流处理流处理抽象接口,可自定义实现setProcessOutputStream(...) 等
StreamPumper流处理单条流的搬运线程体(Runnable)new StreamPumper(is, os, true)
LogOutputStream流处理把流入数据按行回调给你new LogOutputStream()
ExecuteWatchdog超时监控进程执行时长,超时后强制终止ExecuteWatchdog.builder().setTimeout(...).get()
ExecuteResultHandler结果异步执行结果回调接口onProcessComplete(...)、onProcessFailed(...)
OS环境判定判定操作系统家族、名称、架构OS.isFamilyUnix()、OS.isName("linux")

表格解读:一次完整的「执行外部命令」通常只需要前三行——CommandLine 描述、DefaultExecutor 执行、PumpStreamHandler 收流;需要限时再加 ExecuteWatchdog,需要不阻塞再加 ExecuteResultHandler。OS 与 ExecuteStreamHandler 属于按需使用的辅助类。

三、前置条件

本章准备一个独立实验目录并引入 commons-exec-1.6.0.jar,同时备好演示用的目标目录。Commons Exec 零传递依赖,只需一个主 jar 即可编译运行下文的全部示例。

创建目录并下载主 jar:

mkdir -p /opt/exec-lab/lib /opt/exec-lab/src/net/quzuo/demo
curl -fsSL -o /opt/exec-lab/lib/commons-exec-1.6.0.jar https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.6.0/commons-exec-1.6.0.jar
ls -l /opt/exec-lab/lib/

实测回显:

total 68
-rw-r--r-- 1 root root 69316 Oct 13 16:54 commons-exec-1.6.0.jar

再准备一个被命令访问的目标目录,供 ls、grep 等示例使用:

mkdir -p /opt/commons/reports /opt/commons/data
printf "sample report\n" > /opt/commons/reports/q3.txt
printf "id,name\n1,demo\n" > /opt/commons/data/users.csv
printf "readme content\n" > /opt/commons/README.md
ls -1 /opt/commons

实测回显:

README.md
data
reports

⚠️ 注意:本文示例源码统一放在 /opt/exec-lab/src/net/quzuo/demo/,包名为 net.quzuo.demo;源码含中文字符串,编译必须带 -encoding UTF-8,运行必须带 -Dfile.encoding=UTF-8,否则中文会显示为乱码。带包名的类按 javac -encoding UTF-8 -cp 'lib/*' -d classes src/net/quzuo/demo/<类>.java 编译、按 java -Dfile.encoding=UTF-8 -cp 'classes:lib/*' net.quzuo.demo.<类> 运行。

四、CommandLine 详解

本章讲解 CommandLine——描述「执行什么命令、带什么参数」。它有三种构造方式:直接 new CommandLine("可执行文件")、用 CommandLine.parse(整条命令) 解析、或对已有对象 addArgument(...) 追加。

4.1 构造与解析

CommandLine.parse 把整条命令按空白切成「可执行文件 + 参数数组」;toStrings() 返回可执行文件与参数拼接后的完整数组,getArguments() 只返回参数部分。

CommandLine fromParse = CommandLine.parse("ls -l /opt/commons");
System.out.println("parse 后可执行文件: " + fromParse.getExecutable());
System.out.println("parse 后参数数组: " + Arrays.toString(fromParse.getArguments()));
System.out.println("toStrings(): " + Arrays.toString(fromParse.toStrings()));

实测回显(CliDemo 输出节选,仅第 1 段):

===== 1) CommandLine 构造与解析 =====
parse 后可执行文件: ls
parse 后参数数组: [-l, /opt/commons]
toStrings(): [ls, -l, /opt/commons]

getExecutable() 返回 ls,getArguments() 返回 [-l, /opt/commons],与输入一致。

4.2 addArgument 与引号处理

addArgument(String, boolean) 的第二个布尔参数决定「是否对参数做引号处理」:传 true 时会按 shell 规则给含空格的参数加引号,传 false 时原样传递。下例用 sh -c 跑一段含空格的脚本,并把位置参数 $1/$2 逐字加入:

CommandLine cmd = new CommandLine("sh");
cmd.addArgument("-c", false);
cmd.addArgument("echo 参数一: $1; echo 参数二: $2", false);
cmd.addArgument("值一", true);
cmd.addArgument("值二", true);
System.out.println("toStrings(): " + Arrays.toString(cmd.toStrings()));

实测回显(CliDemo 输出节选,仅第 2 段):

===== 2) addArgument 追加与是否处理引号 =====
toStrings(): [sh, -c, echo 参数一: $1; echo 参数二: $2, 值一, 值二]

含空格的脚本被整体作为一个参数传递(false 未加引号),这也是 sh -c 能收到完整脚本的原因。

💡 技术拓展:把命令交给外部执行时,「怎样把参数拼成一条命令」有好几种方式,处理引号与空格的可靠度差别很大。

方式示例空格/引号处理可靠性
字符串硬拼Runtime.exec("sh -c 'echo x'")全靠自己拼,易错存在注入与解析风险
CommandLine.parse(String)CommandLine.parse("ls -l /opt")按空白切分,不解析引号简单命令可用,含引号不可靠
CommandLine + addArgument(arg, true)逐参数追加并加引号库自动按规则加引号推荐,参数可控

本文统一使用 CommandLine + addArgument:实测把带引号的复合命令整条交给 CommandLine.parse("sh -c 'echo hi'") 时,参数被错误切分,进程返回退出码 127;改用「可执行文件与脚本分别 addArgument」后即恢复正常。

4.3 变量替换 setSubstitutionMap

CommandLine 支持把参数里的变量占位(形如 ${GREETING})在取参数时按 setSubstitutionMap 传入的 Map 展开:

Map<String, Object> map = new HashMap<>();
map.put("GREETING", "hello");
map.put("TARGET", "commons-exec");
CommandLine sub = new CommandLine("echo");
sub.addArgument("${GREETING} ${TARGET}", false);
sub.setSubstitutionMap(map);
System.out.println("替换前模板: [echo, ${GREETING} ${TARGET}]");
System.out.println("替换后 toStrings(): " + Arrays.toString(sub.toStrings()));

实测回显(CliDemo 输出节选,仅第 3 段):

===== 3) 变量替换 setSubstitutionMap =====
替换前模板: [echo, ${GREETING} ${TARGET}]
替换后 toStrings(): [echo, hello commons-exec]

模板里的 ${GREETING} ${TARGET} 在 toStrings() 读取时被展开为 hello commons-exec,说明变量替换发生在「读取参数」这一步,原参数串保持不变。

五、DefaultExecutor 详解

本章讲解 DefaultExecutor——执行命令的主体。1.6.0 中无参构造 new DefaultExecutor() 已标记 @Deprecated,推荐用 DefaultExecutor.builder().get() 构建,再用 execute(...) 的三种重载执行。

三种 execute 重载:

重载形态用途
execute(CommandLine)同步执行阻塞到结束,返回退出码或抛 ExecuteException
execute(CommandLine, Map<String,String>)同步 + 环境变量以给定环境执行(全量替换)
execute(CommandLine, ExecuteResultHandler)异步执行立即返回,结果由回调通知

同步执行的最小形态:

DefaultExecutor executor = DefaultExecutor.builder()
        .setExecuteStreamHandler(new PumpStreamHandler(out, err))
        .get();
int exit = executor.execute(CommandLine.parse("echo hello commons exec"));
System.out.println("退出码: " + exit);
System.out.println("isFailure(exit): " + executor.isFailure(exit));

实测回显(HelloExec 输出节选,仅 echo 段):

命令: echo hello commons exec
退出码: 0
hello commons exec
commons-exec 版本: 1.6.0
验证通过

execute 返回退出码 0,isFailure(0) 为 false;末行还从 jar 清单打印出库版本 1.6.0。需要判断「退出码算不算失败」时,用 executor.isFailure(exit);要放宽成功集合可用 setExitValues(new int[]{0, 3})。

六、PumpStreamHandler 与流处理

本章讲解 PumpStreamHandler——负责「收流」,避免子进程写满管道缓冲区而卡死。它有三个构造形态,掌握后即可决定 stdout、stderr 各自的去向。

构造方式stdout 去向stderr 去向stdin
new PumpStreamHandler()System.outSystem.err不写入
new PumpStreamHandler(out, err)outerr不写入
new PumpStreamHandler(out, err, in)outerr从 in 读入写入子进程

把两路输出分别收进内存缓冲:

ByteArrayOutputStream out = new ByteArrayOutputStream();
ByteArrayOutputStream err = new ByteArrayOutputStream();
DefaultExecutor executor = DefaultExecutor.builder()
        .setExecuteStreamHandler(new PumpStreamHandler(out, err))
        .get();
executor.execute(CommandLine.parse("ls -1 /opt/commons"));
System.out.print(out.toString(StandardCharsets.UTF_8));

实测回显(ExecTool 输出节选):

[exit] 0
[stdout]
README.md
data
reports
[stderr]

stdout 被完整捕获、stderr 为空,证明分流构造正确。若用单参数 new PumpStreamHandler(out),则 stdout 与 stderr 合流到同一条流,此时该流必须线程安全,因为两路输出由两个线程并发写入。

七、ExecuteWatchdog 详解

本章讲解 ExecuteWatchdog——超时看门狗。构造时给一个超时 Duration,子进程超过阈值即被 destroyProcess() 强制终止(发 SIGTERM),killedProcess() 可事后查询是否被杀。

DefaultExecutor executor = DefaultExecutor.builder()
        .setExecuteStreamHandler(new PumpStreamHandler(out, err))
        .get();
executor.setWatchdog(ExecuteWatchdog.builder()
        .setTimeout(Duration.ofSeconds(2))
        .get());
executor.execute(CommandLine.parse("sleep 5"));

实测回显(ExecTool -t 2 sleep 5):

[fail] Process exited with an error: 143 (Exit value: 143)
[stdout]
[stderr]

子进程在 2 秒处被终止,抛出 ExecuteException,退出值 143(128 + 15,即 SIGTERM),并非业务退出码。要「永不超时、由代码显式杀进程」时,可传 ExecuteWatchdog.INFINITE_TIMEOUT(-1)再在需要时调用 destroyProcess()。注意 ExecuteWatchdog.builder() 不设超时的默认值是无限,Watchdog.builder() 的默认值才是 30 秒,两者不要混淆。

八、ExecuteResultHandler 异步结果

本章讲解 ExecuteResultHandler——异步结果回调接口,让主线程提交命令后立即返回。它有两个方法:onProcessComplete(int) 在成功时回调,onProcessFailed(ExecuteException) 在失败时回调。

自己实现一个回调,用 CountDownLatch 等待结果:

CountDownLatch latch = new CountDownLatch(1);
ExecuteResultHandler handler = new ExecuteResultHandler() {
    @Override
    public void onProcessComplete(int exitValue) {
        System.out.println("onProcessComplete 回调, exitValue=" + exitValue);
        latch.countDown();
    }
    @Override
    public void onProcessFailed(ExecuteException e) {
        System.out.println("onProcessFailed 回调: " + e.getMessage());
        latch.countDown();
    }
};
CommandLine cmd = new CommandLine("sh");
cmd.addArgument("-c", false);
cmd.addArgument("sleep 1; echo 自定义回调任务结束", false);
executor.execute(cmd, handler);
System.out.println("execute 已返回,主线程继续");
latch.await();

实测回显(CliAsyncDemo 输出节选,仅 A 段):

===== A) 自定义 ExecuteResultHandler =====
execute 已返回,主线程继续
自定义回调任务结束
onProcessComplete 回调, exitValue=0

execute 立即返回、主线程打印「已返回」后继续,子进程结束后才触发 onProcessComplete。库自带 DefaultExecuteResultHandler 已实现该接口并提供 waitFor(),适合「异步提交、按需同步取值」:

DefaultExecuteResultHandler handler = new DefaultExecuteResultHandler();
executor.execute(failCmd, handler);
handler.waitFor();
System.out.println("失败场景 hasResult(): " + handler.hasResult());
System.out.println("失败场景 getExitValue(): " + handler.getExitValue());
System.out.println("失败场景 getException(): " + handler.getException());

实测回显(CliAsyncDemo 输出节选,仅 B 段):

===== B) DefaultExecuteResultHandler 内置实现 =====
内置处理器成功任务结束
成功场景 hasResult(): true
成功场景 getExitValue(): 0
成功场景 getException(): null
内置处理器失败任务结束
失败场景 hasResult(): true
失败场景 getExitValue(): 7
失败场景 getException(): org.apache.commons.exec.ExecuteException: Process exited with an error: 7 (Exit value: 7)

一个必须留意的实测结论:DefaultExecuteResultHandler.waitFor() 不重抛异常。失败任务 exit 7 时,waitFor() 正常返回,失败信息留在 getException() 里(成功场景该值为 null),退出码从 getExitValue() 读取。判断成败请查 getException(),而不要指望 waitFor() 抛出。

九、ExecuteStreamHandler 接口与自定义实现

本章讲解 ExecuteStreamHandler——PumpStreamHandler 是它的默认实现,必要时可自行实现,把三条流接到任意地方。接口要覆盖四个方法:setProcessOutputStream(InputStream)、setProcessErrorStream(InputStream)、setProcessInputStream(OutputStream)、start() / stop()。

下面用 StreamPumper 起两个线程分别收 stdout 与 stderr,stop() 时 join 线程:

static class CollectingStreamHandler implements ExecuteStreamHandler {
    private final ByteArrayOutputStream out = new ByteArrayOutputStream();
    private final ByteArrayOutputStream err = new ByteArrayOutputStream();
    private Thread outThread;
    private Thread errThread;

    @Override
    public void setProcessOutputStream(InputStream is) {
        outThread = new Thread(new StreamPumper(is, out, true));
        outThread.start();
    }
    @Override
    public void setProcessErrorStream(InputStream is) {
        errThread = new Thread(new StreamPumper(is, err, true));
        errThread.start();
    }
    @Override
    public void setProcessInputStream(OutputStream os) throws IOException {
        os.close();
    }
    @Override
    public void start() {
    }
    @Override
    public void stop() throws IOException {
        join(outThread);
        join(errThread);
    }
    private void join(Thread thread) {
        if (thread == null) {
            return;
        }
        try {
            thread.join();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
    String stdout() {
        return out.toString(StandardCharsets.UTF_8);
    }
    String stderr() {
        return err.toString(StandardCharsets.UTF_8);
    }
}

setProcessInputStream 里直接 os.close(),表示子进程不需要标准输入、关闭管道即触发 EOF。用它执行一条同时输出 stdout 与 stderr 的脚本:

CollectingStreamHandler handler = new CollectingStreamHandler();
DefaultExecutor executor = DefaultExecutor.builder()
        .setExecuteStreamHandler(handler)
        .get();
CommandLine cmd = new CommandLine("sh");
cmd.addArgument("-c", false);
cmd.addArgument("echo 自定义处理器捕获的 STDOUT; echo 自定义处理器捕获的 STDERR 1>&2", false);
int exit = executor.execute(cmd);
System.out.println("退出码: " + exit);
System.out.println("自定义 handler stdout: " + handler.stdout().trim());
System.out.println("自定义 handler stderr: " + handler.stderr().trim());

实测回显(StreamHandlerDemo):

退出码: 0
自定义 handler stdout: 自定义处理器捕获的 STDOUT
自定义 handler stderr: 自定义处理器捕获的 STDERR

两条流分别落进自定义的缓冲,说明自定义 ExecuteStreamHandler 生效。若不自定义,直接用 PumpStreamHandler 更省事;LogOutputStream 则适合「边执行边按行打印」的场景。

十、OS 家族判定

本章讲解 OS——判定当前操作系统,常用于「按平台选择命令写法」。它提供三类方法:家族判定 isFamilyUnix()/isFamilyWindows() 等、名称判定 isName(...)、架构判定 isArch(...)。

System.out.println("isFamilyUnix():     " + OS.isFamilyUnix());
System.out.println("isFamilyWindows():  " + OS.isFamilyWindows());
System.out.println("isName(\"linux\"):     " + OS.isName("linux"));
System.out.println("isName(\"Linux\"):     " + OS.isName("Linux"));
System.out.println("isArch(\"amd64\"):     " + OS.isArch("amd64"));

实测回显(CliDemo 输出节选,仅第 4 段):

===== 4) OS 家族判定 =====
isFamilyUnix():     true
isFamilyWindows():  false
isFamilyWinNT():    false
isFamilyMac():      false
isFamilyOS2():      false
isFamilyOpenVms():  false
isName("linux"):     true
isName("Linux"):     false
isArch("amd64"):     true

逐项解读:isFamilyUnix() 为 true;isName("linux") 为 true 而 isName("Linux") 为 false——名称判定区分大小写,必须传小写;isArch("amd64") 为 true。

💡 技术拓展:判断操作系统有几种思路,精确度与可移植性不同。

方式代码特点
系统属性System.getProperty("os.name")返回 Linux 等原始串,大小写/厂商不定,需自己匹配
家族判定OS.isFamilyUnix()按族归类,跨发行版稳定
名称判定OS.isName("linux")精确比对,区分大小写
架构判定OS.isArch("amd64")区分 amd64/x86 等架构

本文示例用 OS.isFamilyUnix() 做跨平台分流(稳定、不挑大小写),需要精确到具体系统名时才用 OS.isName,并注意传小写。

十一、自写执行工具 ExecTool 实战

本章把前面的类组合成一个通用小工具 ExecTool——java -cp 直接运行,接收「可执行文件 + 参数」,可选超时,跑完打印退出码与 stdout/stderr。它是「用核心类拼工具」的完整示例。

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.ExecuteException;
import org.apache.commons.exec.ExecuteWatchdog;
import org.apache.commons.exec.PumpStreamHandler;

import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.time.Duration;

public class ExecTool {

    public static void main(String[] args) throws Exception {
        if (args.length == 0) {
            System.out.println("用法: java ExecTool [-t 超时秒数] <可执行文件> [参数列表]");
            return;
        }
        long timeoutSeconds = 0;
        int start = 0;
        if ("-t".equals(args[0])) {
            timeoutSeconds = Long.parseLong(args[1]);
            start = 2;
        }
        CommandLine cmd = new CommandLine(args[start]);
        for (int i = start + 1; i < args.length; i++) {
            cmd.addArgument(args[i], false);
        }
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ByteArrayOutputStream err = new ByteArrayOutputStream();
        DefaultExecutor executor = DefaultExecutor.builder()
                .setExecuteStreamHandler(new PumpStreamHandler(out, err))
                .get();
        if (timeoutSeconds > 0) {
            executor.setWatchdog(ExecuteWatchdog.builder()
                    .setTimeout(Duration.ofSeconds(timeoutSeconds)).get());
        }
        try {
            int exit = executor.execute(cmd);
            System.out.println("[exit] " + exit);
        } catch (ExecuteException e) {
            System.out.println("[fail] " + e.getMessage());
        }
        System.out.println("[stdout]");
        System.out.print(out.toString(StandardCharsets.UTF_8));
        System.out.println("[stderr]");
        System.out.print(err.toString(StandardCharsets.UTF_8));
    }
}

该工具无包名,编译与运行都用 java -cp 直指定类路径:

cd /opt/exec-lab
javac -encoding UTF-8 -cp 'lib/*' -d outtool ExecTool.java

编译输出 class 到 outtool,无回显。先看不带参数的用法回显:

java -Dfile.encoding=UTF-8 -cp 'outtool:lib/*' ExecTool

实测回显:

用法: java ExecTool [-t 超时秒数] <可执行文件> [参数列表]

执行目录列举命令:

java -Dfile.encoding=UTF-8 -cp 'outtool:lib/*' ExecTool ls -1 /opt/commons

实测回显:

[exit] 0
[stdout]
README.md
data
reports
[stderr]

执行带中文参数的回显命令:

java -Dfile.encoding=UTF-8 -cp 'outtool:lib/*' ExecTool echo 参数一 参数二

实测回显:

[exit] 0
[stdout]
参数一 参数二
[stderr]

加上 -t 2 的 2 秒超时执行 sleep 5:

java -Dfile.encoding=UTF-8 -cp 'outtool:lib/*' ExecTool -t 2 sleep 5

实测回显:

[fail] Process exited with an error: 143 (Exit value: 143)
[stdout]
[stderr]

四组回显覆盖了工具的用法提示、正常执行、中文传参与超时失败三个分支,说明 CommandLine、DefaultExecutor、PumpStreamHandler、ExecuteWatchdog 四类协作正确。

十二、常见调用形态与注意事项

本章汇总几类高频调用形态与对应注意点,便于照用。

场景调用形态注意点
同步取输出new PumpStreamHandler(out, err) + execute(cmd)默认仅退出码 0 视为成功,其他会抛 ExecuteException
限时执行executor.setWatchdog(ExecuteWatchdog.builder().setTimeout(...).get())ExecuteWatchdog.builder() 默认无限;Watchdog.builder() 默认 30 秒
不阻塞execute(cmd, resultHandler)自定义接口需自行等待;DefaultExecuteResultHandler.waitFor() 不重抛异常
注入环境变量execute(cmd, env)Map 全量替换,先复制 EnvironmentUtils.getProcEnvironment() 再追加
放宽成功码executor.setExitValues(new int[]{0, 3})声明后 isFailure(3) 变 false
跨平台分流OS.isFamilyUnix() / OS.isName("linux")isName 区分大小写,传小写

两条最容易踩的经验:一是退出码默认只认 0,业务命令返回 1、3、7 都要显式声明,否则会被当异常;二是异步结果别指望 waitFor() 抛异常,判成败要看 getException()。

十三、常见问题

Q1:CommandLine.parse("sh -c 'echo x'") 为什么执行报错? parse 按空白切分且不解析引号,带引号的复合命令会被错误拆开,实测返回退出码 127。改用「可执行文件与脚本分别 addArgument」即可(见第四节 4.2)。

Q2:new DefaultExecutor() 编译出现 deprecation 警告怎么办? 1.6.0 起无参构造已弃用,改用 DefaultExecutor.builder().get()(见第五节)。

Q3:DefaultExecuteResultHandler.waitFor() 为什么没抛异常? 实测该方法不重抛异常,失败信息保留在 getException();exit 7 时 getExitValue() 返回 7、getException() 为 ExecuteException(见第八节 B 段回显)。

Q4:OS.isName("Linux") 为什么返回 false? isName 区分大小写,实测 isName("linux") 为 true、isName("Linux") 为 false(见第十节),请传小写。

Q5:超时被杀后退出码为什么是 143? 143 = 128 + 15,代表被 SIGTERM 终止(见第七节回显 Exit value: 143),不是业务退出码;配合 killedProcess() 判断是否因超时被杀。

十四、参考链接

  1. Apache Commons Exec 官方主页:https://commons.apache.org/proper/commons-exec/
  2. 官方 javadoc(1.6.0 API 文档):https://commons.apache.org/proper/commons-exec/apidocs/
  3. CommandLine 类文档:https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/CommandLine.html
  4. DefaultExecutor 类文档:https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/DefaultExecutor.html
  5. ExecuteWatchdog 类文档:https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/ExecuteWatchdog.html
  6. ExecuteResultHandler 类文档:https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/ExecuteResultHandler.html
  7. ExecuteStreamHandler 类文档:https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/ExecuteStreamHandler.html
  8. OS 类文档:https://commons.apache.org/proper/commons-exec/apidocs/org/apache/commons/exec/OS.html
  9. Maven 中央仓库 1.6.0 目录:https://repo1.maven.org/maven2/org/apache/commons/commons-exec/1.6.0/