我正在尝试将进程的输出重定向到另一个进程的输入。管道。在Windows DOS shell中执行此操作时,它看起来像
C:\> dir /s/b . | findstr dat$
但是我在Java中尝试这样的命令,到目前为止它看起来像:
Stopwatch sw = Stopwatch.createStarted();
ProcessBuilder source = new ProcessBuilder("cmd", "/S/D/c", "dir", "/s/b", ".");
ProcessBuilder target = new ProcessBuilder("cmd", "/S/D/c", "findstr", "dat$");
source.directory(new File("C:/"));
target.directory(source.directory());
// I am messing with the below lines, nothing is working
source.redirectInput(target.redirectInput());
source.redirectOutput(ProcessBuilder.Redirect.PIPE);
source.redirectOutput(target.redirectInput());
source.redirectInput(target.redirectOutput());
target.redirectOutput(source.redirectInput());
Process pSource = source.start();
Process pTarget = target.start();
log.debug("Running {} | {}", source.command(), target.command());
try (BufferedReader br = new BufferedReader(new InputStreamReader(pTarget.getInputStream()))) {
String line;
while ((line = br.readLine()) != null)
log.debug("{}", line);
} finally {
log.debug("Ending process {} with exist code {} in time {}", target.command(),
pTarget.destroyForcibly().exitValue(), sw);
}
但是我发现readLine上的代码停顿了,所以有些东西在这里不起作用。如何正确使用IO重定向?
答案 0 :(得分:3)
redirectInput()
resp,redirectOutput()
接受或退回的对象描述了某项政策;它们不代表实际渠道
因此,通过语句source.redirectInput(target.redirectInput())
,您只是指定两个进程应该具有相同的策略,而不是链接通道。
事实上,在Java 8中直接链接两个进程的通道是不可能的。你可以做的最好的事情是实现类似的效果,就是启动一个后台线程,它将读取第一个进程的输出并将其写入第二个进程进程'输入:
static List<Process> doPipeJava8() throws IOException {
Process pSource = new ProcessBuilder("cmd", "/S/D/c", "dir", "/s/b", ".")
.redirectInput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT)
.start();
Process pTarget;
try {
pTarget = new ProcessBuilder("cmd", "/S/D/c", "findstr", "dat$")
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)
.start();
} catch(Throwable t) {
pSource.destroyForcibly();
throw t;
}
new Thread(() -> {
try(InputStream srcOut = pSource.getInputStream();
OutputStream dstIn = pTarget.getOutputStream()) {
byte[] buffer = new byte[1024];
while(pSource.isAlive() && pTarget.isAlive()) {
int r = srcOut.read(buffer);
if(r > 0) dstIn.write(buffer, 0, r);
}
} catch(IOException ex) {}
}).start();
return Arrays.asList(pSource, pTarget);
}
这会将错误通道,第一个进程的输入通道和最后一个进程的输出通道配置为INHERIT
,以便它们使用我们的启动进程控制台。第一个进程'输出和第二个进程'输入保持默认PIPE
,这意味着建立管道到我们的启动进程,因此我们有责任将数据从一个管道复制到另一个。
该方法可以用作
List<Process> sub = doPipeJava8();
Process pSource = sub.get(0), pTarget = sub.get(1);
pSource.waitFor();
pTarget.waitFor();
如果我们从.redirectOutput(ProcessBuilder.Redirect.INHERIT)
进程的构建器中删除pTarget
,我们可以阅读最终输出:
List<Process> sub = doPipeJava8();
Process pSource = sub.get(0), pTarget = sub.get(1);
List<String> result = new BufferedReader(new InputStreamReader(pTarget.getInputStream()))
.lines().collect(Collectors.toList());
Java 9是第一个支持在子进程之间建立管道的Java版本。它简化了解决方案
static List<Process> doPipeJava9() throws IOException {
return ProcessBuilder.startPipeline(
List.of(new ProcessBuilder("cmd", "/S/D/c", "dir", "/s/b", ".")
.redirectInput(ProcessBuilder.Redirect.INHERIT)
.redirectError(ProcessBuilder.Redirect.INHERIT),
new ProcessBuilder("cmd", "/S/D/c", "findstr", "dat$")
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.INHERIT)) );
}
它与其他解决方案¹相同;上面的示例配置为让第一个进程从控制台读取(如果需要),最后一个进程写入控制台。同样,如果我们省略上一个流程构建器中的.redirectOutput(ProcessBuilder.Redirect.INHERIT)
,我们可以读取最后一个流程的输出。
¹,除非它尽可能使用系统的原生管道功能