核心方法
使用 Runtime
类(传统方式)
try { // 执行命令 Process process = Runtime.getRuntime().exec("cmd /c dir C:\\"); // 读取命令输出 BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream(), "GBK") // Windows中文环境用GBK编码 ); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } // 等待命令执行完成(阻塞当前线程) int exitCode = process.waitFor(); System.out.println("Exit Code: " + exitCode); } catch (Exception e) { e.printStackTrace(); }
- 关键参数说明:
cmd /c [命令]
:执行后关闭CMD窗口(/k
表示执行后保留窗口)。process.getInputStream()
:获取命令的标准输出流。process.waitFor()
:阻塞当前线程直到命令结束。
使用 ProcessBuilder
类(推荐方式)
try { // 构建命令及参数(避免手动拼接字符串) ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "ping", "www.baidu.com"); builder.redirectErrorStream(true); // 合并标准错误和标准输出 // 启动进程 Process process = builder.start(); // 读取输出 try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream(), "GBK")) ) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } // 检查执行结果 if (process.waitFor() == 0) { System.out.println("命令执行成功"); } else { System.out.println("命令执行失败"); } } catch (Exception e) { e.printStackTrace(); }
- 优势:
- 支持参数列表化(避免命令注入风险)。
- 可设置工作目录、环境变量。
- 灵活的重定向输入/输出/错误流。
关键注意事项
-
编码问题:
- Windows CMD默认使用
GBK
编码,Linux/Mac使用UTF-8
,错误编码会导致输出乱码:new InputStreamReader(process.getInputStream(), "GBK")
- Windows CMD默认使用
-
阻塞与超时控制:
process.waitFor()
会无限期阻塞线程,建议使用带超时的版本:boolean finished = process.waitFor(10, TimeUnit.SECONDS); if (!finished) { process.destroy(); // 强制终止进程 }
-
错误流处理:
- 必须同时读取标准输出流和错误流,否则进程可能因缓冲区满而挂起:
// 分别启动两个线程读取stdout和stderr new Thread(() -> readStream(process.getInputStream())).start(); new Thread(() -> readStream(process.getErrorStream())).start();
- 必须同时读取标准输出流和错误流,否则进程可能因缓冲区满而挂起:
-
命令注入风险:
- 错误做法:拼接用户输入的字符串(高危!)
Runtime.getRuntime().exec("cmd /c " + userInput); // 可能执行恶意命令
- 正确做法:使用
ProcessBuilder
分割参数:ProcessBuilder builder = new ProcessBuilder("cmd", "/c", "echo", userInput);
- 错误做法:拼接用户输入的字符串(高危!)
-
跨平台兼容性:
- Linux/Mac需替换命令前缀:
String[] command = System.getProperty("os.name").toLowerCase().contains("win") ? new String[]{"cmd.exe", "/c", "dir"} : new String[]{"/bin/sh", "-c", "ls"};
- Linux/Mac需替换命令前缀:
完整示例:执行Python脚本并捕获输出
public class ExecuteCMD { public static void main(String[] args) { try { ProcessBuilder builder = new ProcessBuilder("python", "C:/test.py"); builder.directory(new File("C:/")); // 设置工作目录 Process process = builder.start(); // 异步读取输出 StringBuilder output = new StringBuilder(); Thread readThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream())) ) { String line; while ((line = reader.readLine()) != null) { output.append(line).append("\n"); } } catch (IOException e) { e.printStackTrace(); } }); readThread.start(); // 等待命令结束 int exitCode = process.waitFor(); readThread.join(); // 确保输出读取完成 System.out.println("输出内容:\n" + output); System.out.println("退出码: " + exitCode); } catch (Exception e) { e.printStackTrace(); } } }
安全与最佳实践
- 最小权限原则:避免以管理员权限执行命令。
- 资源释放:用
try-with-resources
确保流关闭。 - 日志记录:记录执行的命令、参数和退出码,便于审计。
- 输入验证:严格校验用户输入,禁止直接拼接命令。
引用说明:
- Oracle官方文档:
ProcessBuilder
,Runtime.exec()
- 编码问题参考:The Pitfalls of Runtime.exec()
- 安全建议:OWASP Command Injection
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/7121.html