應用程式需呼叫外部命令列工具(如資料庫備份 dump、壓縮 7z),並根據執行結果(成功/失敗)進行後續流程控制。關鍵在於:如何正確捕獲進程退出碼,並區分標準輸出與錯誤輸出。
@echo off
dump -uroot -p1234 --database gbase > 1.sql 2>Nul
if %errorlevel%==0 goto next
goto error
:next
echo OK
goto end
:error
echo ERROR
:end
> 1.sql:標準輸出導向檔案。2>Nul:錯誤輸出丟棄(Windows 空裝置)。%errorlevel%:上一條命令的退出碼,0 表成功。echo OK/ERROR 讓呼叫端從標準輸出讀取狀態字串。public class BatchExecutor {
public static void main(String[] args) throws Exception {
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "backup.bat");
pb.redirectErrorStream(true); // 合併 stderr 到 stdout,簡化讀取
Process process = pb.start();
// 非同步讀取輸出,避免緩衝區塞滿導致死鎖
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream(), "MS950"))) { // 中文編碼
String line;
while ((line = reader.readLine()) != null) {
System.out.println("[STDOUT] " + line);
if (line.contains("OK")) {
System.out.println(">> 批次檔執行成功");
} else if (line.contains("ERROR")) {
System.out.println(">> 批次檔執行失敗");
}
}
}
int exitCode = process.waitFor();
System.out.println("進程退出碼: " + exitCode);
}
}
using System.Diagnostics;
public class BatchRunner {
public static void Run() {
var startInfo = new ProcessStartInfo {
FileName = "cmd.exe",
Arguments = "/c backup.bat",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true,
StandardOutputEncoding = Encoding.GetEncoding(950), // Big5
StandardErrorEncoding = Encoding.GetEncoding(950)
};
using (var process = Process.Start(startInfo)) {
// 非同步讀取雙流
var outputTask = process.StandardOutput.ReadToEndAsync();
var errorTask = process.StandardError.ReadToEndAsync();
process.WaitForExit();
string output = outputTask.Result;
string error = errorTask.Result;
Console.WriteLine("[STDOUT]\n" + output);
if (!string.IsNullOrEmpty(error))
Console.WriteLine("[STDERR]\n" + error);
Console.WriteLine($"進程退出碼: {process.ExitCode}");
if (output.Contains("OK")) Console.WriteLine(">> 成功");
else if (output.Contains("ERROR")) Console.WriteLine(">> 失敗");
}
}
}
| 問題 | 解決方案 |
|---|---|
| 輸出緩衝區塞滿導致進程卡死 | Java: 獨立執行緒讀取 InputStream/ErrorStream;C#: ReadToEndAsync 或事件式 OutputDataReceived |
| 中文亂碼 | 指定編碼:Java MS950/CP950,C# Encoding.GetEncoding(950) |
| 需區分 stdout/stderr | Java: ProcessBuilder.redirectErrorStream(false) 分別讀取;C#: 同時開啟 RedirectStandardOutput/Error |
| 批次檔內多條命令,僅最後一條的 errorlevel 有效 | 每條關鍵命令後立即 if %errorlevel% neq 0 exit /b %errorlevel% 向外部傳遞 |
| 需取得批次檔內部變數值 | 批次檔 echo ::RESULT::%VAR%,呼叫端解析特定前綴行 |
核心原則:非同步讀取雙流 + 指定編碼 + 解析約定輸出標記。Java 建議使用 ProcessBuilder 替代舊版 Runtime.exec;C# 則善用 ProcessStartInfo 細粒度控制。若批次邏輯複雜,建議改寫為 Python/PowerShell 腳本,再由 Java/C# 呼叫,維護性更佳。