转载

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,也应在需要时验证产物是否存在、是否符合格式和数量要求。调用方不应把一个空列表同时用于表示“没有输出”和“执行失败”。

来源与参考

正文到此结束