两种核心执行方式
Runtime.exec() 方法
Java最传统的命令执行方式,通过java.lang.Runtime类实现:
try {
// 执行命令
Process process = Runtime.getRuntime().exec("ls -l /home");
// 读取命令输出
BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
// 等待命令结束并获取退出码
int exitCode = process.waitFor();
System.out.println("Exit Code: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
关键点:
- 直接传入字符串命令(如
"ping 127.0.0.1") - 需手动处理输入/输出流,避免缓冲区阻塞
- 调用
waitFor()阻塞当前线程直至命令结束
ProcessBuilder 类(推荐)
Java 1.5+引入的更安全、灵活的方式:
try {
// 构建命令及参数(避免注入风险)
ProcessBuilder builder = new ProcessBuilder();
builder.command("ls", "-l", "/home"); // 参数独立传递
// 重定向错误流到标准输出
builder.redirectErrorStream(true);
// 启动进程
Process process = builder.start();
// 读取输出
try (BufferedReader reader = new BufferedReader(
new InputStreamReader(process.getInputStream())
)) {
reader.lines().forEach(System.out::println);
}
// 检查执行结果
int exitCode = process.waitFor();
if (exitCode == 0) {
System.out.println("Command executed successfully!");
} else {
System.out.println("Error! Exit code: " + exitCode);
}
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
优势对比:
| 特性 | Runtime.exec() | ProcessBuilder |
|——————|———————–|————————|
| 参数安全性 | 易受命令注入攻击 | 参数分离传递更安全 |
| 流控制 | 需手动处理 | 支持redirectOutput() |
| 工作目录设置 | 不支持 | directory(File dir) |
| 环境变量管理 | 复杂 | environment()方法 |
关键注意事项
安全风险防范
-
命令注入防护:
// 错误做法(用户输入直接拼接) String userInput = "; rm -rf /"; Runtime.getRuntime().exec("ls " + userInput); // 灾难性后果! // 正确做法(使用ProcessBuilder分割参数) ProcessBuilder builder = new ProcessBuilder(); builder.command("ls", userInput); // 参数会被安全处理
流处理必知
- 避免死锁: 命令输出可能阻塞缓冲区,必须读取输入流和错误流:
// 启动进程后立即读取流 InputStream input = process.getInputStream(); InputStream error = process.getErrorStream(); // 建议:使用单独线程消费这些流
超时控制
防止命令无限期运行:
process.waitFor(30, TimeUnit.SECONDS); // 设置30秒超时
if (process.isAlive()) {
process.destroyForcibly(); // 强制终止
}
进阶技巧
环境变量管理
ProcessBuilder builder = new ProcessBuilder("echo", "$JAVA_HOME");
Map<String, String> env = builder.environment();
env.put("JAVA_HOME", "/usr/lib/jvm/java-11");
builder.start();
重定向输出到文件
builder.redirectOutput(new File("output.log"));
builder.redirectError(new File("error.log"));
执行管道命令
通过Shell解释器实现:
ProcessBuilder builder = new ProcessBuilder("/bin/sh", "-c", "ps aux | grep java");
应用场景与风险提示
典型用例:
- 服务器部署时执行Git/Pull
- 调用系统工具(如ImageMagick处理图片)
- 运行Python/Shell脚本
风险警示:
- 权限最小化: 避免用root权限执行命令
- 输入校验: 严格过滤用户输入的参数
- 日志记录: 记录所有执行的命令及结果
- 替代方案: 优先使用Java原生库(如用
Files.copy()代替cp命令)
- 简单场景用
Runtime.exec(),复杂任务选ProcessBuilder - 必须处理流和退出码,否则可能导致资源泄漏或程序挂起
- 安全是首要考量,任何外部输入都应视为不可信
引用说明:本文代码示例基于Oracle官方Java 17文档,安全建议参考OWASP命令注入防护指南,实践时请结合具体环境调整,Windows系统需替换命令为
dir等原生指令。
原创文章,发布者:酷番叔,转转请注明出处:https://cloud.kd.cn/ask/6433.html