Java 执行外部进程:参数、输出、退出码与超时
围绕 Windows 与 Linux 外部命令调用整理 Java ProcessBuilder 示例,说明参数传递、输出处理、退出码和超时清理。
从命令字符串转向进程契约
原转载示例使用 Runtime.exec 和线程池读取输出,但混合了静态方法与实例方法,且对等待超时、退出码和进程清理的表达不够完整。本次用一个较小的 ProcessBuilder 示例解释核心职责。
调用方应提供可信的可执行文件及参数列表,分别指定工作目录、日志文件和超时时间。不要把来自 HTTP 请求的任意文本拼到 cmd /c 或 /bin/sh -c 中。
Java 8 示例:输出重定向与有界等待
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public final class ProcessRunner {
public static int run(List<String> command, File directory,
File logFile, long timeoutSeconds)
throws IOException, InterruptedException, TimeoutException {
if (command == null || command.isEmpty() || timeoutSeconds <= 0) {
throw new IllegalArgumentException("Invalid process request");
}
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(directory);
builder.redirectErrorStream(true);
builder.redirectOutput(ProcessBuilder.Redirect.appendTo(logFile));
Process process = builder.start();
try {
// 此接口不向子进程提供交互输入。
process.getOutputStream().close();
if (!process.waitFor(timeoutSeconds, TimeUnit.SECONDS)) {
throw new TimeoutException("Process exceeded its time budget");
}
return process.exitValue();
} finally {
if (process.isAlive()) {
process.destroyForcibly();
}
}
}
}
示例把标准错误合并到标准输出并写入日志文件,避免为了返回完整输出而无限积累内存。日志目录需事先存在且可写,同时应制定日志大小和轮转策略。
超时后发出强制结束请求,不等于已经保证整个进程树退出。Java 8 中对子进程树、后台派生进程和平台作业对象的处理需要额外设计。因此,示例适合启动受控的单进程工具,不承诺清理所有派生任务。
Windows 和 Linux 如何传参数
// javaExecutable 应是已核对的 java.exe 或 java 的绝对路径。
List<String> command = java.util.Arrays.asList(javaExecutable, "-version");
一个参数使用一个列表元素,避免自行按空格切分路径。Shell 内建命令、管道和重定向属于另一套执行语义;确实需要时应单独设计固定脚本和受控参数,而不是将本接口改成接受任意 Shell 命令。
不要混淆四种结果
| 结果 | 含义 |
|---|---|
| 创建进程失败 | 路径、权限或运行环境等问题,应保留 IOException |
| 进程以非零值退出 | 工具执行失败或工具定义的其他状态,需要读取其约定 |
| 等待超时 | 超过调用方预算,需清理并报告,不返回假成功 |
| 调用线程被中断 | 将 InterruptedException 交给调用方决定取消策略 |
即使退出码为 0,也应在需要时验证产物是否存在、是否符合格式和数量要求。调用方不应把一个空列表同时用于表示“没有输出”和“执行失败”。
来源与参考
正文到此结束