首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 執行 Shell 命令工具類實戰:支援超時控制與結果封裝

     編輯:Java 相關知識

為什麼需要封裝 Runtime.exec?

Java 原生 Runtime.getRuntime().exec() 雖能啟動子進程,但缺乏超時控制、輸出流非阻塞讀取、錯誤流處理、退出碼統一封裝等工程化能力。直接使用容易導致進程掛起、記憶體洩漏、結果丟失等問題。本文提供一個生產就緒的 CommandHelper 工具類,解決上述痛點。

核心類設計

CommandResult:統一結果封裝

public class CommandResult {
    public static final int EXIT_VALUE_TIMEOUT = -1;
    private String output;   // 標準輸出
    private String error;    // 錯誤輸出
    private int exitValue;   // 退出碼

    // getter/setter 省略
}

CommandHelper:核心執行引擎

public class CommandHelper {
    public static int DEFAULT_TIMEOUT = 30000; // 預設 30 秒
    public static final int DEFAULT_INTERVAL = 1000;
    private static long START;

    public static CommandResult exec(String command) 
            throws IOException, InterruptedException {
        Process process = Runtime.getRuntime().exec(command);
        CommandResult result = wait(process);
        if (process != null) process.destroy();
        return result;
    }

    private static CommandResult wait(Process process) 
            throws InterruptedException, IOException {
        // 非阻塞讀取 stdout/stderr,避免緩衝區滿導致死鎖
        BufferedReader errorReader = new BufferedReader(
            new InputStreamReader(process.getErrorStream()));
        BufferedReader inputReader = new BufferedReader(
            new InputStreamReader(process.getInputStream()));

        START = System.currentTimeMillis();
        boolean finished = false;

        while (true) {
            // 超時檢查
            if (System.currentTimeMillis() - START >= DEFAULT_TIMEOUT) {
                CommandResult r = new CommandResult();
                r.setExitValue(EXIT_VALUE_TIMEOUT);
                r.setOutput("Command process timeout");
                return r;
            }

            // 嘗試獲取退出碼,判斷進程是否結束
            try {
                int exitVal = process.exitValue(); // 若未結束拋 IllegalThreadStateException
                finished = true;
            } catch (IllegalThreadStateException e) {
                finished = false;
                Thread.sleep(DEFAULT_INTERVAL);
                continue;
            }

            if (finished) {
                CommandResult r = new CommandResult();
                r.setExitValue(process.waitFor());

                // 讀取錯誤流
                if (errorReader.ready()) {
                    StringBuilder eb = new StringBuilder();
                    String line;
                    while ((line = errorReader.readLine()) != null) eb.append(line).append("\n");
                    r.setError(eb.toString());
                }
                // 讀取標準輸出
                if (inputReader.ready()) {
                    StringBuilder ob = new StringBuilder();
                    String line;
                    while ((line = inputReader.readLine()) != null) ob.append(line).append("\n");
                    r.setOutput(ob.toString());
                }
                return r;
            }
        }
    }
}

使用範例

public static void main(String[] args) {
    try {
        int timeout = Integer.parseInt(args[0]); // 傳入超時毫秒數
        CommandHelper.DEFAULT_TIMEOUT = timeout;
        CommandResult result = CommandHelper.exec("mkdir testdir");
        if (result != null) {
            System.out.println("Output: " + result.getOutput());
            System.out.println("Error:  " + result.getError());
            System.out.println("Exit:   " + result.getExitValue());
        }
    } catch (IOException | InterruptedException e) {
        e.printStackTrace();
    }
}

實戰踩坑記錄與解決方案

問題原因解決方案
SSH 遠端命令需密碼互動 預設 SSH 要求交互式輸入密碼 使用 sshpass -p password ssh user@host 'cmd' 或配置免密鑰登入
IDE 直接 Run 失敗,提示權限不足 IDE 進程無終端環境,無法分配 TTY 打包為 JAR,在終端執行 java -jar xxx.jar
部分命令(如 cd、source)無效 非內建命令、需 Shell 內建指令 改用 bash -c "cmd1; cmd2" 或引入 Ganymed SSH-2 / JSch 庫

進階擴展建議

  • 支援環境變量注入:ProcessBuilder.environment()
  • 工作目錄指定:ProcessBuilder.directory(File)
  • 異步回調:結合 CompletableFuture 實現非阻塞調用
  • 管道與重導向:ProcessBuilder.redirectOutput()

總結

CommandHelper 以 50 行核心代碼 解決了超時控制、流讀取死鎖、結果封裝三大核心問題,適合作為專案基礎設施庫的基石。對於複雜的 SSH 自動化場景,建議直接使用 JSch 或 Apache MINA SSHD 等成熟庫,避免重複造輪子。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved