如何通过Apache Commons exec实现进程的动态交互式输入?
问题描述
我正尝试使用Apache Commons Exec实现与命令行进程的交互,但在代码中遇到了无法动态向进程传递命令的问题。以下是我的代码:
ByteArrayOutputStream out = new ByteArrayOutputStream(); ByteArrayOutputStream ins = new ByteArrayOutputStream(); OutputStreamWriter ow = new OutputStreamWriter(ins); BufferedWriter writer = new BufferedWriter(ow); ByteArrayInputStream in = new ByteArrayInputStream(ins.toByteArray()); PumpStreamHandler psh = new PumpStreamHandler(out, null, in); CommandLine cl = CommandLine.parse(initProcess); DefaultExecutor exec = new DefaultExecutor(); DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); exec.setStreamHandler(psh); try { exec.execute(cl, resultHandler); int i = 0; while (true) { String o = out.toString(); if (!o.trim().isEmpty()) { System.out.println(o); out.reset(); } // --- PROBLEM start --- if (i == 3) { writer.write(internalProcessCommand); // 试过带或不带换行符都不行 writer.flush(); writer.close(); // 甚至试过直接用ins.write(internalProcessCommand.getBytes()) } // --- PROBLEM end --- Thread.sleep(3000); i++; } } catch (ExecuteException e) { System.err.println(e.getMessage()); }
我的逻辑是每隔3秒读取并打印进程输出流内容后清空流。当前核心问题在于:传递给PumpStreamHandler的输入流in无法支持动态写入命令——我需要像CLI交互那样持续向进程发送命令。当直接用System.in作为参数时可以正常从控制台输入,但通过代码传递字符串完全无效。
补充说明:
- 我也曾尝试使用
PipedInputStream和PipedOutputStream,但发现只有关闭PipedOutputStream后才能读取数据,无法复用,因此无法实现持续交互。
解决方案
问题的核心在于**ByteArrayInputStream是静态的**:一旦创建,它的内容就固定为创建时ins.toByteArray()的字节数组,后续写入ins的内容不会同步到in中,自然无法传递给进程。
针对这个问题,正确的做法是使用PipedInputStream和PipedOutputStream,并且避免提前关闭输出流,同时确保写入操作在独立线程中执行(因为管道流的读写操作会互相阻塞)。以下是修正后的实现:
// 初始化管道流:PipedOutputStream写入的数据会实时传递给PipedInputStream PipedInputStream processInput = new PipedInputStream(); PipedOutputStream appOutput = new PipedOutputStream(processInput); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(appOutput)); ByteArrayOutputStream processOutput = new ByteArrayOutputStream(); PumpStreamHandler psh = new PumpStreamHandler(processOutput, null, processInput); CommandLine cl = CommandLine.parse(initProcess); DefaultExecutor exec = new DefaultExecutor(); DefaultExecuteResultHandler resultHandler = new DefaultExecuteResultHandler(); exec.setStreamHandler(psh); try { exec.execute(cl, resultHandler); int i = 0; while (true) { // 读取并打印进程输出 String output = processOutput.toString(); if (!output.trim().isEmpty()) { System.out.println(output); processOutput.reset(); } // 动态向进程发送命令 if (i == 3) { writer.write(internalProcessCommand); writer.newLine(); // 注意:大多数交互式进程需要换行符来确认命令 writer.flush(); // 必须flush才能让数据立即进入管道 // 不要关闭writer/appOutput,否则后续无法继续写入 } Thread.sleep(3000); i++; } } catch (Exception e) { System.err.println(e.getMessage()); } finally { // 程序结束时再关闭资源 try { writer.close(); appOutput.close(); processInput.close(); } catch (IOException e) { e.printStackTrace(); } }
关键改进点:
- 使用
PipedInputStream和PipedOutputStream替代静态的ByteArrayInputStream,实现数据的实时传递。 - 不提前关闭输出流,保持管道打开以支持持续写入。
- 向进程发送命令时添加换行符(
writer.newLine()),因为大多数交互式CLI进程需要换行来识别命令输入完成。 - 确保每次写入后调用
flush(),让数据立即进入管道供进程读取。
另外需要注意:如果你的写入操作比较频繁或者数据量较大,建议把写入逻辑放到单独的线程中执行,避免主线程被管道的阻塞操作卡住。
内容的提问来源于stack exchange,提问作者MichalVales
相关产品推荐
相关产品推荐

