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

Java 呼叫 Linux Shell 腳本的方法

     編輯:Java 相關知識

Runtime.exec / ProcessBuilder 執行 Shell 腳本完整指南與避坑

Java 調用外部 Shell 腳本是常見需求,但直接使用 Runtime.getRuntime().exec() 容易遇到「進程卡死」、「權限不足」、「環境變量缺失」等問題。本文提供正確姿勢與最佳實踐。

核心問題:緩衝區填滿導致死鎖

子進程的標準輸出 和錯誤輸出 由 Java 進程通過管道讀取。若 Java 端不及時讀取,管道緩衝區填滿,子進程寫入阻塞,導致 waitFor() 永久等待。**必須在調用 waitFor() 前啟動線程消費兩個流。**

推薦方案:ProcessBuilder + 線程消費流 (通用、健壯)


public class ShellExecutor {
    public static int execute(String scriptPath, String... args) throws IOException, InterruptedException {
        List command = new ArrayList<>();
        command.add("/bin/bash"); // 或 /bin/sh
        command.add(scriptPath);
        command.addAll(Arrays.asList(args));

        ProcessBuilder pb = new ProcessBuilder(command);
        pb.redirectErrorStream(true); // 關鍵:合併錯誤流到標準輸出,簡化處理
        // pb.directory(new File("/work/dir")); // 設置工作目錄
        // pb.environment().put("CUSTOM_VAR", "value"); // 設置環境變量

        Process process = pb.start();

        // 異步消費輸出流 (防止緩衝區填滿)
        Thread outputThread = new Thread(() -> {
            try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) {
                String line;
                while ((line = reader.readLine()) != null) {
                    System.out.println("[SCRIPT] " + line); // 或寫入日誌
                }
            } catch (IOException e) { e.printStackTrace(); }
        });
        outputThread.start();

        int exitCode = process.waitFor(); // 等待結束
        outputThread.join(1000); // 確保輸出讀取完成

        return exitCode;
    }

    public static void main(String[] args) throws Exception {
        int code = execute("/home/user/scripts/backup.sh", "arg1", "arg2");
        System.out.println("腳本退出碼: " + code);
    }
}

關鍵最佳實踐

  1. 合併錯誤流:pb.redirectErrorStream(true) 避免需啟動兩個讀取線程。
  2. 指定完整路徑:腳本路徑、解釋器路徑 (/bin/bash) 使用絕對路徑。
  3. 賦予執行權限:腳本文件需 chmod +x script.sh,或在命令中顯式調用 bash script.sh。
  4. 環境變量隔離:子進程默認繼承父進程環境,可通過 pb.environment() 修改/清理。
  5. 超時控制:Java 9+ process.waitFor(timeout, TimeUnit) 避免無限等待。
  6. 參數注入安全:使用 List 命令列表而非單一字符串,避免 Shell 注入漏洞 (參數不會被 Shell 解析)。

舊版 Runtime.exec 寫法 (不推薦)


// 易出錯:需手動處理兩個流、參數分割、路徑解析
String cmd = "bash /path/to/script.sh arg1";
Process p = Runtime.getRuntime().exec(cmd);
// 必須啟動兩個線程分別讀取 p.getInputStream() 和 p.getErrorStream()
int exit = p.waitFor();

常見錯誤排查

  • Permission denied:腳本無執行權限 (chmod +x) 或調用用戶無權限。
  • No such file or directory:路徑錯誤、腳本內部命令路徑錯誤 (建議腳本內用絕對路徑)。
  • Exit code 127:命令未找到 (PATH 環境變量問題)。
  • 中文亂碼:指定 InputStreamReader(..., StandardCharsets.UTF_8) 或腳本輸出編碼一致的編碼。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved