ProcessBuilder : 기본 스레드를 차단하지 않고 시작된 프로세스의 stdout 및 stderr 전달
다음과 같이 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();
}
srcEOF 가 수행되기 때문에 스레드는 하위 프로세스가 완료되면 자동으로 죽습니다 .
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();
}
Here the output stream is redirected to System.out and the error stream is logged on the error level by the logger.
It's as simple as following:
File logFile = new File(...);
ProcessBuilder pb = new ProcessBuilder()
.command("somecommand", "arg1", "arg2")
processBuilder.redirectErrorStream(true);
processBuilder.redirectOutput(logFile);
by .redirectErrorStream(true) you tell process to merge error and output stream and then by .redirectOutput(file) you redirect merged output to a file.
Update:
I did manage to do this as follows:
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
}
Now you're able to see both outputs from main and asyncOut threads in System.out
I too can use only Java 6. I used @EvgeniyDorofeev's thread scanner implementation. In my code, after a process finishes, I have to immediately execute two other processes that each compare the redirected output (a diff-based unit test to ensure stdout and stderr are the same as the blessed ones).
The scanner threads don't finish soon enough, even if I waitFor() the process to complete. To make the code work correctly, I have to make sure the threads are joined after the process finishes.
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();
}
});
}
Simple java8 solution with capturing both outputs and reactive processing using 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);
}
});
}
And the usage:
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();
Thread thread = new Thread(() -> {
new BufferedReader(
new InputStreamReader(inputStream,
StandardCharsets.UTF_8))
.lines().forEach(...);
});
thread.start();
Your custom code goes instead of the ...
By default, the created subprocess does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods getOutputStream(), getInputStream(), and getErrorStream(). The parent process uses these streams to feed input to and get output from the subprocess. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, or even deadlock.
'Programing' 카테고리의 다른 글
| click () 대신 jQuery on ()을 사용하는 이유 (0) | 2020.09.18 |
|---|---|
| pandas.DataFrame을 뒤집는 올바른 방법? (0) | 2020.09.18 |
| Javascript의 MSIE 및 addEventListener 문제? (0) | 2020.09.18 |
| SQLAlchemy 식에서 컴파일 된 원시 SQL 쿼리를 가져 오는 방법은 무엇입니까? (0) | 2020.09.18 |
| htaccess로 디렉토리 목록 거부 (0) | 2020.09.18 |