本文详解如何在 Java 中通过 ProcessBuilder 或 Runtime.exec 向外部脚本(如 Bash 程序)可靠传递用户输入,重点解决管道符 | 失效、命令不解析 shell 特性等问题,并提供安全、可维护的标准输入注入方案。
本文详解如何在 java 中通过 processbuilder 或 runtime.exec 向外部脚本(如 bash 程序)可靠传递用户输入,重点解决管道符 `|` 失效、命令不解析 shell 特性等问题,并提供安全、可维护的标准输入注入方案。
Java 的 Runtime.exec() 和 ProcessBuilder 默认不经过 shell 解析(即不调用 /bin/sh 或 /bin/bash),因此像 echo cd | ./script 这类含管道、重定向或变量扩展的命令会被整体当作一个程序名处理——| 变成字面参数,而非 shell 操作符。这就是你看到 cd|./script 被原样输出的根本原因。
✅ 正确做法:分离执行与输入,手动写入 stdin
最健壮、跨平台且符合 Java 设计意图的方式是:直接启动目标脚本(如 ./script),然后通过 Process.getOutputStream() 向其标准输入写入数据。这种方式绕过 shell 依赖,避免注入风险,也更易调试。
以下是推荐实现(使用 ProcessBuilder,更清晰可控):
import java.io.*;
import java.util.*;
public class TryIt {
public static void main(String[] args) {
try {
// 1. 构建进程:仅执行脚本本身(不带 echo 或管道)
ProcessBuilder pb = new ProcessBuilder("./script");
pb.directory(new File(System.getProperty("user.dir")));
pb.redirectErrorStream(true); // 合并 stderr 到 stdout,便于统一读取
Process process = pb.start();
// 2. 向脚本 stdin 写入密码(注意:必须 flush!)
try (OutputStream stdin = process.getOutputStream();
PrintWriter writer = new PrintWriter(stdin, true)) {
writer.println("cd"); // 发送密码,自动换行并刷新
}
// 3. 读取脚本输出
try (InputStream stdout = process.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(stdout))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// 4. 等待进程结束(可选,但建议用于同步)
int exitCode = process.waitFor();
System.out.println("Script exited with code: " + exitCode);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}⚠️ 关键注意事项
- PrintWriter 必须启用 auto-flush(构造时传 true),或显式调用 flush();否则输入可能滞留在缓冲区,脚本因无输入而阻塞。
- 不要依赖 redirectErrorStream(false) + 单独读 getErrorStream():若脚本将提示信息(如 "Enter password:")输出到 stderr,而你只读 stdout,会导致死锁。redirectErrorStream(true) 是安全默认。
- 避免 Runtime.exec(String command):它内部仍会做简单 shell 分词,但行为不一致且不可控;始终优先使用 ProcessBuilder 并传入 String[] 参数列表。
- 切勿拼接用户输入到命令字符串中(如 pb.command("bash", "-c", "echo " + userInput + " | ./script")):存在严重命令注入漏洞。
? 替代方案:显式调用 bash(仅限必要场景)
若脚本逻辑强依赖 shell 功能(如复杂管道、环境变量展开),可显式启动 bash:
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
立即学习“Java免费学习笔记(深入)”;
ProcessBuilder pb = new ProcessBuilder("/bin/bash", "-c", "echo cd | ./script");但此方式:
- 丧失可移植性(Windows 无 /bin/bash);
- 输入仍需通过 getOutputStream() 注入(因为 echo 是子进程,| 左侧的 echo 无法由 Java 直接控制);
- 增加调试难度和安全风险。
因此,手动写入 stdin 是首选方案——它精准对应“用户交互”语义,且完全由 Java 控制流管理。
✅ 总结
| 方案 | 是否推荐 | 原因 |
|---|---|---|
| echo x \| ./script 在 exec/ProcessBuilder 中直接执行 | ❌ | | 不被解析,作为字面参数传递 |
| bash -c "echo x \| ./script" | ⚠️ 仅临时调试 | 引入 shell 依赖与注入风险,非必要不采用 |
| 直接运行 ./script + process.getOutputStream().write(...) | ✅ 强烈推荐 | 精准、安全、可测试、跨平台、符合 Unix 进程模型 |
掌握这一模式,你不仅能解决密码输入问题,还能可靠集成任何需要交互式 stdin 的外部工具(如 gpg --decrypt、mysql CLI、自定义 CLI 程序等)。

















