다음과 같이 ProcessBuilder를 사용하여 Java로 프로세스를 구축하고 있습니다.
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
.redirectErrorStream(true);
Process p = pb.start();
InputStream stdOut = p.getInputStream();
이제 내 문제는 다음과 같습니다. 해당 프로세스의 stdout 및 / 또는 stderr를 통과하는 모든 것을 캡처하고 System.out
비동기 적으로 리디렉션하고 싶습니다 . 프로세스와 출력 리디렉션이 백그라운드에서 실행되기를 원합니다. 지금까지이 작업을 수행하는 유일한 방법은 지속적으로 읽을 새 스레드를 수동으로 생성 한 stdOut
다음 적절한 write()
메서드 를 호출하는 것입니다 System.out
.
new Thread(new Runnable(){
public void run(){
byte[] buffer = new byte[8192];
int len = -1;
while((len = stdOut.read(buffer)) > 0){
System.out.write(buffer, 0, len);
}
}
}).start();
그 접근 방식이 작동하지만 약간 더러워 보입니다. 또한 올바르게 관리하고 종료 할 수있는 스레드가 하나 더 있습니다. 이 작업을 수행하는 더 좋은 방법이 있습니까?
답변
Java 6 또는 이전 버전 에서 유일한 방법은 소위 StreamGobbler
(당신이 만들기 시작)를 사용하는 것입니다.
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), "ERROR");
// any output?
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), "OUTPUT");
// start gobblers
outputGobbler.start();
errorGobbler.start();
…
private class StreamGobbler extends Thread {
InputStream is;
String type;
private StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}
@Override
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String line = null;
while ((line = br.readLine()) != null)
System.out.println(type + "> " + line);
}
catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
Java 7의 경우 Evgeniy Dorofeev의 답변을 참조하십시오.
답변
를 사용 ProcessBuilder.inheritIO
하여 하위 프로세스 표준 I / O의 소스 및 대상을 현재 Java 프로세스와 동일하게 설정합니다.
Process p = new ProcessBuilder().inheritIO().command("command1").start();
Java 7이 옵션이 아닌 경우
public static void main(String[] args) throws Exception {
Process p = Runtime.getRuntime().exec("cmd /c dir");
inheritIO(p.getInputStream(), System.out);
inheritIO(p.getErrorStream(), System.err);
}
private static void inheritIO(final InputStream src, final PrintStream dest) {
new Thread(new Runnable() {
public void run() {
Scanner sc = new Scanner(src);
while (sc.hasNextLine()) {
dest.println(sc.nextLine());
}
}
}).start();
}
src
EOF 가 수행되기 때문에 스레드는 하위 프로세스가 완료되면 자동으로 죽습니다 .
답변
Consumer
출력을 한 줄씩 처리 (예 : 로그 기록) 하는를 제공 할 수있는 Java 8 람다가 포함 된 유연한 솔루션입니다 . run()
확인 된 예외가 발생하지 않는 한 줄짜리입니다. 구현 Runnable
하는 Thread
대신 다른 답변이 제안하는 것처럼 확장 할 수 있습니다 .
class StreamGobbler implements Runnable {
private InputStream inputStream;
private Consumer<String> consumeInputLine;
public StreamGobbler(InputStream inputStream, Consumer<String> consumeInputLine) {
this.inputStream = inputStream;
this.consumeInputLine = consumeInputLine;
}
public void run() {
new BufferedReader(new InputStreamReader(inputStream)).lines().forEach(consumeInputLine);
}
}
그런 다음 예를 들어 다음과 같이 사용할 수 있습니다.
public void runProcessWithGobblers() throws IOException, InterruptedException {
Process p = new ProcessBuilder("...").start();
Logger logger = LoggerFactory.getLogger(getClass());
StreamGobbler outputGobbler = new StreamGobbler(p.getInputStream(), System.out::println);
StreamGobbler errorGobbler = new StreamGobbler(p.getErrorStream(), logger::error);
new Thread(outputGobbler).start();
new Thread(errorGobbler).start();
p.waitFor();
}
여기서 출력 스트림은로 리디렉션되고 System.out
오류 스트림은 logger
.
답변
다음과 같이 간단합니다.
File logFile = new File(...);
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(logFile);
.redirectErrorStream (true)에 의해 프로세스에 오류와 출력 스트림 을 병합 하도록 지시 한 다음 .redirectOutput (file)에 의해 병합 된 출력을 파일로 리디렉션합니다.
최신 정보:
나는 이것을 다음과 같이 관리했다.
public static void main(String[] args) {
// Async part
Runnable r = () -> {
ProcessBuilder pb = new ProcessBuilder().command("...");
// Merge System.err and System.out
pb.redirectErrorStream(true);
// Inherit System.out as redirect output stream
pb.redirectOutput(ProcessBuilder.Redirect.INHERIT);
try {
pb.start();
} catch (IOException e) {
e.printStackTrace();
}
};
new Thread(r, "asyncOut").start();
// here goes your main part
}
이제 System.out 에서 main 및 asyncOut 스레드의 출력을 모두 볼 수 있습니다.
답변
다음을 사용하여 출력과 반응 처리를 모두 캡처하는 간단한 java8 솔루션 CompletableFuture
:
static CompletableFuture<String> readOutStream(InputStream is) {
return CompletableFuture.supplyAsync(() -> {
try (
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
){
StringBuilder res = new StringBuilder();
String inputLine;
while ((inputLine = br.readLine()) != null) {
res.append(inputLine).append(System.lineSeparator());
}
return res.toString();
} catch (Throwable e) {
throw new RuntimeException("problem with executing program", e);
}
});
}
그리고 사용법 :
Process p = Runtime.getRuntime().exec(cmd);
CompletableFuture<String> soutFut = readOutStream(p.getInputStream());
CompletableFuture<String> serrFut = readOutStream(p.getErrorStream());
CompletableFuture<String> resultFut = soutFut.thenCombine(serrFut, (stdout, stderr) -> {
// print to current stderr the stderr of process and return the stdout
System.err.println(stderr);
return stdout;
});
// get stdout once ready, blocking
String result = resultFut.get();
답변
더 나은 ProcessBuilder 인 zt-exec를 제공하는 라이브러리가 있습니다. 이 라이브러리는 당신이 요구하는 것을 정확하게 할 수 있습니다.
ProcessBuilder 대신 zt-exec를 사용하는 코드는 다음과 같습니다.
종속성을 추가하십시오.
<dependency>
<groupId>org.zeroturnaround</groupId>
<artifactId>zt-exec</artifactId>
<version>1.11</version>
</dependency>
코드 :
new ProcessExecutor()
.command("somecommand", "arg1", "arg2")
.redirectOutput(System.out)
.redirectError(System.err)
.execute();
라이브러리 문서는 여기에 있습니다 : https://github.com/zeroturnaround/zt-exec/
답변
저도 Java 6 만 사용할 수 있습니다. @EvgeniyDorofeev의 스레드 스캐너 구현을 사용했습니다. 내 코드에서 프로세스가 완료된 후 각각 리디렉션 된 출력을 비교하는 두 개의 다른 프로세스를 즉시 실행해야합니다 (stdout 및 stderr이 축복받은 것과 동일한 지 확인하기위한 diff 기반 단위 테스트).
프로세스가 완료 될 때까지 waitFor ()하더라도 스캐너 스레드가 빨리 완료되지 않습니다. 코드가 올바르게 작동하도록하려면 프로세스가 완료된 후 스레드가 결합되었는지 확인해야합니다.
public static int runRedirect (String[] args, String stdout_redirect_to, String stderr_redirect_to) throws IOException, InterruptedException {
ProcessBuilder b = new ProcessBuilder().command(args);
Process p = b.start();
Thread ot = null;
PrintStream out = null;
if (stdout_redirect_to != null) {
out = new PrintStream(new BufferedOutputStream(new FileOutputStream(stdout_redirect_to)));
ot = inheritIO(p.getInputStream(), out);
ot.start();
}
Thread et = null;
PrintStream err = null;
if (stderr_redirect_to != null) {
err = new PrintStream(new BufferedOutputStream(new FileOutputStream(stderr_redirect_to)));
et = inheritIO(p.getErrorStream(), err);
et.start();
}
p.waitFor(); // ensure the process finishes before proceeding
if (ot != null)
ot.join(); // ensure the thread finishes before proceeding
if (et != null)
et.join(); // ensure the thread finishes before proceeding
int rc = p.exitValue();
return rc;
}
private static Thread inheritIO (final InputStream src, final PrintStream dest) {
return new Thread(new Runnable() {
public void run() {
Scanner sc = new Scanner(src);
while (sc.hasNextLine())
dest.println(sc.nextLine());
dest.flush();
}
});
}