Java 調用 Windows CMD 執行命令是常見需求,但直接用 Runtime.exec() 拼接命令字符串極易踩坑:目錄切換不生效、多命令連接失敗、跨盤符執行異常。本文通過實戰案例,揭示 CMD 命令解析機制,給出生產可用的最佳實踐方案。
// 錯誤示範:企圖切換目錄後執行 dir
String cmd = "cmd /c cd D:\\img && dir /b";
Runtime.getRuntime().exec(cmd); // 不生效!
原因:CMD 的 cd 僅改變當前會話的工作目錄,且不切換盤符。若當前盤符不是 D:,cd D:\img 僅記錄路徑,實際仍停留在原盤符,後續 dir 執行於原目錄。
// 正確:三步走策略
String cmd = "cmd /c D: && cd D:\\img && dir /b";
Process process = Runtime.getRuntime().exec(cmd);
驗證方法:直接打開 CMD 視窗依次輸入上述三條命令,觀察行為一致。
cmd /c:啟動一個子進程會話,執行字符串命令後退出。cd 路徑:僅修改路徑部分,不改變盤符。若需跨盤符,必須先執行 D:(盤符+冒號)切換當前盤符。&&:命令鏈操作符,前一條成功(返回碼 0)才執行後一條。對比 Linux:Linux 無盤符概念,
cd /path直接生效,cd /path && ls完美工作。Windows 歷史遺留的盤符設計導致此差異。
import java.io.*;
import java.util.*;
public class CmdExecutor {
public static void execute(String... commands) throws IOException, InterruptedException {
// 1. 使用 ProcessBuilder,自動處理參數轉義、空格、特殊字符
ProcessBuilder pb = new ProcessBuilder("cmd", "/c");
// 2. 構建命令鏈:盤符切換 + 目錄切換 + 業務命令
StringBuilder cmdChain = new StringBuilder();
for (int i = 0; i < commands.length; i++) {
if (i > 0) cmdChain.append(" && ");
cmdChain.append(commands[i]);
}
pb.command().add(cmdChain.toString());
// 3. 關鍵:繼承父進程環境變量,或顯式設置工作目錄
// pb.directory(new File("D:\\img")); // 更推薦:直接設置工作目錄,避免 cd 命令
pb.inheritIO(); // 直接繼承標準輸入/輸出/錯誤流,避免緩衝區阻塞
Process process = pb.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("CMD 執行失敗,退出碼: " + exitCode);
}
}
public static void main(String[] args) throws Exception {
// 推薦方式:直接指定工作目錄,無需 cd 命令
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", "dir /b");
pb.directory(new File("D:\\img"));
pb.inheritIO();
Process p = pb.start();
p.waitFor();
}
}
Runtime.exec("cmd /c ...") | 字符串拼接易出錯、流處理麻煩、跨平台差 | ⭐ |
| ProcessBuilder + 命令鏈 | 仍依賴 CMD 解析邏輯 | ⭐⭐⭐ |
| ProcessBuilder.directory(File) + 單命令 | **最乾淨**:JVM 直接設置子進程工作目錄,無需 cd、盤符切換,天然跨平台 | ⭐⭐⭐⭐⭐ |
\\ → 實際命令 \,正則/特殊字符雙重轉義。getInputStream()/getErrorStream() 導致緩衝區滿、進程掛起。用 inheritIO() 或單獨線程消費流。process.waitFor(timeout, TimeUnit)(JDK 8+),防止命令卡死。runas。Java 執行 CMD 多命令,核心在於理解 CMD 會話上下文(盤符+目錄) 與 命令鏈 && 語義。生產環境強烈建議使用 ProcessBuilder.directory() 直接設置工作目錄,配合單一業務命令,徹底規避 cd、盤符切換、轉義等所有坑點,代碼更簡潔、健壯、可移植。