在Java中执行DOS命令行操作(通常指Windows系统的命令提示符)是系统交互、自动化脚本或外部程序调用的常见需求,以下将详细说明两种核心方法(Runtime.exec()
和ProcessBuilder
),结合安全实践和完整示例,帮助开发者高效安全地实现功能。
方法1:使用 Runtime.exec()
Runtime
类提供执行系统命令的简单接口,适用于基础场景。
步骤:
- 获取
Runtime
实例 - 执行命令并获取
Process
对象 - 读取命令输出流和错误流
- 等待命令执行完成
- 处理返回值和异常
示例代码:
import java.io.BufferedReader; import java.io.InputStreamReader; public class RuntimeExample { public static void main(String[] args) { try { // 1. 定义DOS命令(示例:列出当前目录文件) String command = "cmd.exe /c dir"; // 2. 执行命令 Process process = Runtime.getRuntime().exec(command); // 3. 获取输出流 BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream(), "GBK") // 中文系统需指定编码 ); // 4. 打印输出 String line; while ((line = reader.readLine()) != null) { System.out.println(line); } // 5. 等待命令结束并检查状态 int exitCode = process.waitFor(); System.out.println("命令执行结束,退出码: " + exitCode); } catch (Exception e) { e.printStackTrace(); } } }
方法2:使用 ProcessBuilder
(推荐)
ProcessBuilder
提供更灵活的参数控制、环境变量管理和安全特性。
优势:
- 支持命令参数拆分(避免注入风险)
- 可重定向输入/输出流
- 设置工作目录和环境变量
示例代码:
import java.io.BufferedReader; import java.io.InputStreamReader; public class ProcessBuilderExample { public static void main(String[] args) { try { // 1. 拆分命令参数(避免直接拼接字符串) ProcessBuilder builder = new ProcessBuilder("cmd.exe", "/c", "dir", "C:\\Users"); // 2. 设置工作目录(可选) // builder.directory(new File("D:\\target")); // 3. 执行命令并获取Process对象 Process process = builder.start(); // 4. 读取输出(使用try-with-resources自动关闭流) try (BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream(), "GBK") )) { String line; while ((line = reader.readLine()) != null) { System.out.println(line); } } // 5. 检查执行状态 int exitCode = process.waitFor(); if (exitCode == 0) { System.out.println("命令成功执行!"); } else { System.err.println("错误退出码: " + exitCode); } } catch (Exception e) { e.printStackTrace(); } } }
关键注意事项
-
编码问题
Windows中文环境默认使用GBK
编码,需显式指定:new InputStreamReader(process.getInputStream(), "GBK")
-
命令注入防御
错误做法:拼接用户输入(高危!)String userInput = args[0]; Runtime.getRuntime().exec("cmd /c dir " + userInput); // 可能被注入恶意命令
正确做法:使用
ProcessBuilder
拆分参数:ProcessBuilder builder = new ProcessBuilder("cmd", "/c", "dir", userInput);
-
流处理阻塞
-
必须读取输出流和错误流,否则进程可能阻塞。
-
可通过多线程并行读取:
new Thread(() -> { // 读取输出流 }).start(); new Thread(() -> { // 读取错误流(process.getErrorStream()) }).start();
-
-
跨平台兼容
- Windows命令前缀:
cmd.exe /c
- Linux/macOS命令前缀:
/bin/sh -c
- 可通过系统属性动态适配:
String os = System.getProperty("os.name").toLowerCase(); if (os.contains("win")) { builder.command("cmd.exe", "/c", "dir"); } else { builder.command("/bin/sh", "-c", "ls"); }
- Windows命令前缀:
完整实战案例
需求:执行ping
命令并解析结果
import java.io.BufferedReader; import java.io.InputStreamReader; public class PingExample { public static void main(String[] args) { 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) { if (line.contains("TTL=")) { System.out.println("成功收到响应: " + line); } } } int exitCode = process.waitFor(); System.out.println("Ping测试完成,退出码: " + exitCode); } catch (Exception e) { e.printStackTrace(); } } }
安全与最佳实践
-
权限控制
- 避免以管理员权限执行命令(除非必要)。
- 使用Java安全管理器(
SecurityManager
)限制敏感操作。
-
超时机制
防止命令长时间阻塞:if (!process.waitFor(30, TimeUnit.SECONDS)) { process.destroy(); // 强制终止 System.err.println("命令执行超时"); }
-
资源释放
使用try-with-resources
或手动关闭流:try (BufferedReader reader = ...) { ... } // 自动关闭
-
日志记录
记录命令执行详情(如参数、退出码),便于审计和调试。
- 简单场景:用
Runtime.exec()
快速实现。 - 复杂需求:优先选
ProcessBuilder
(安全性、灵活性更优)。 - 核心原则:
✅ 拆分命令参数防注入
✅ 处理流阻塞和编码问题
✅ 添加超时和错误处理
✅ 考虑跨平台兼容性
通过合理选择API并遵循安全规范,Java可高效可靠地集成DOS命令行功能,适用于批处理、系统管理及自动化测试等场景。
引用说明参考Oracle官方文档《ProcessBuilder》及《Runtime》,并结合OWASP命令注入防护指南编写。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/9145.html