|
@@ -0,0 +1,145 @@
|
|
|
|
|
+package space.anyi.process;
|
|
|
|
|
+
|
|
|
|
|
+import java.io.BufferedReader;
|
|
|
|
|
+import java.io.File;
|
|
|
|
|
+import java.io.IOException;
|
|
|
|
|
+import java.io.InputStream;
|
|
|
|
|
+import java.io.InputStreamReader;
|
|
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
|
|
+import java.nio.file.Files;
|
|
|
|
|
+import java.nio.file.Path;
|
|
|
|
|
+import java.nio.file.Paths;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+
|
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * 示例二补充:子进程标准输出与错误输出的重定向。
|
|
|
|
|
+ *
|
|
|
|
|
+ * 三个案例点各用一个 method 演示:
|
|
|
|
|
+ * <ul>
|
|
|
|
|
+ * <li>errorStreamMerged() —— 相互重定向(stderr 合并进 stdout)</li>
|
|
|
|
|
+ * <li>redirectToFile() —— 标准流重定向到文件</li>
|
|
|
|
|
+ * <li>redirectEnum() —— Redirect 枚举 PIPE / INHERIT / DISCARD 的解释与验证</li>
|
|
|
|
|
+ * </ul>
|
|
|
|
|
+ * 子进程为 src/main/resources/code/io/Main.java。
|
|
|
|
|
+ */
|
|
|
|
|
+public class ProcessRedirectExample {
|
|
|
|
|
+ private static final Logger log = LoggerFactory.getLogger(ProcessRedirectExample.class);
|
|
|
|
|
+ private static final File WORK_DIR = Paths.get(System.getProperty("user.dir"),
|
|
|
|
|
+ "src", "main", "resources", "code", "io").toFile();
|
|
|
|
|
+
|
|
|
|
|
+ public static void main(String[] args) throws Exception {
|
|
|
|
|
+ Jdk.compileFixture(WORK_DIR);
|
|
|
|
|
+ errorStreamMerged();
|
|
|
|
|
+ redirectToFile();
|
|
|
|
|
+ redirectEnum();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 案例点:相互重定向。
|
|
|
|
|
+ * redirectErrorStream(true):子进程的 stderr 合并写入 stdout 管道,
|
|
|
|
|
+ * 合并后的整体通过 getInputStream() 读取,不再有独立的 stderr 流。
|
|
|
|
|
+ */
|
|
|
|
|
+ private static void errorStreamMerged() throws Exception {
|
|
|
|
|
+ log.info("== 相互重定向:redirectErrorStream(true) ==");
|
|
|
|
|
+ Process process = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
|
|
|
|
|
+ .directory(WORK_DIR)
|
|
|
|
|
+ .redirectErrorStream(true)
|
|
|
|
|
+ .start();
|
|
|
|
|
+ // 只读这一路合并流,就能同时看到 stdout(启动横幅)和 stderr(EOF 统计)的内容
|
|
|
|
|
+ Thread merged = drain("merged", process.getInputStream());
|
|
|
|
|
+ process.getOutputStream().close();
|
|
|
|
|
+ merged.join();
|
|
|
|
|
+ process.waitFor();
|
|
|
|
|
+ log.info("退出码: {}", process.exitValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 案例点:重定向到文件。
|
|
|
|
|
+ * stdin 从文件读取、stdout / stderr 写入文件;子进程结束后读回文件内容校验三条流。
|
|
|
|
|
+ */
|
|
|
|
|
+ private static void redirectToFile() throws Exception {
|
|
|
|
|
+ log.info("== 重定向到文件 ==");
|
|
|
|
|
+ Path dir = Files.createTempDirectory("proc-redirect");
|
|
|
|
|
+ Path input = dir.resolve("input.txt");
|
|
|
|
|
+ Path stdout = dir.resolve("stdout.txt");
|
|
|
|
|
+ Path stderr = dir.resolve("stderr.txt");
|
|
|
|
|
+ // 准备 stdin 的输入文件(两行内容)
|
|
|
|
|
+ Files.write(input, List.of("来自文件的输入行", "第二行来自文件"));
|
|
|
|
|
+
|
|
|
|
|
+ Process process = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
|
|
|
|
|
+ .directory(WORK_DIR)
|
|
|
|
|
+ .redirectInput(input.toFile()) // stdin 从文件读
|
|
|
|
|
+ .redirectOutput(stdout.toFile()) // stdout 写到文件
|
|
|
|
|
+ .redirectError(stderr.toFile()) // stderr 写到文件
|
|
|
|
|
+ .start();
|
|
|
|
|
+ process.waitFor();
|
|
|
|
|
+
|
|
|
|
|
+ // 读回文件校验:stdout 应有两行回显,stderr 应有一行统计
|
|
|
|
|
+ log.info("退出码: {}", process.exitValue());
|
|
|
|
|
+ log.info("stdout 文件内容: {}", Files.readAllLines(stdout));
|
|
|
|
|
+ log.info("stderr 文件内容: {}", Files.readAllLines(stderr));
|
|
|
|
|
+
|
|
|
|
|
+ // 清理临时文件
|
|
|
|
|
+ Files.deleteIfExists(input);
|
|
|
|
|
+ Files.deleteIfExists(stdout);
|
|
|
|
|
+ Files.deleteIfExists(stderr);
|
|
|
|
|
+ Files.deleteIfExists(dir);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 案例点:Redirect 枚举的解释与验证。
|
|
|
|
|
+ * <ul>
|
|
|
|
|
+ * <li>PIPE —— 默认值:子进程输出进入管道,父进程用 getInputStream() 读取;</li>
|
|
|
|
|
+ * <li>INHERIT —— 子进程输出直接打印到父进程的控制台;</li>
|
|
|
|
|
+ * <li>DISCARD —— 子进程输出被直接丢弃。</li>
|
|
|
|
|
+ * </ul>
|
|
|
|
|
+ */
|
|
|
|
|
+ private static void redirectEnum() throws Exception {
|
|
|
|
|
+ // PIPE:默认行为(不写 redirect* 时就是 PIPE),逐行读回子进程输出
|
|
|
|
|
+ log.info("== Redirect.PIPE(默认,父进程读取) ==");
|
|
|
|
|
+ Process pipe = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
|
|
|
|
|
+ .directory(WORK_DIR)
|
|
|
|
|
+ .redirectOutput(ProcessBuilder.Redirect.PIPE)
|
|
|
|
|
+ .start();
|
|
|
|
|
+ Thread pipeDrain = drain("pipe", pipe.getInputStream());
|
|
|
|
|
+ pipe.getOutputStream().close();
|
|
|
|
|
+ pipeDrain.join();
|
|
|
|
|
+ pipe.waitFor();
|
|
|
|
|
+
|
|
|
|
|
+ // INHERIT:子进程 stdout/stderr 直接输出到父进程控制台,不进入管道
|
|
|
|
|
+ log.info("== Redirect.INHERIT(以下两行将直接打印到父控制台) ==");
|
|
|
|
|
+ Process inherit = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
|
|
|
|
|
+ .directory(WORK_DIR)
|
|
|
|
|
+ .redirectOutput(ProcessBuilder.Redirect.INHERIT)
|
|
|
|
|
+ .redirectError(ProcessBuilder.Redirect.INHERIT)
|
|
|
|
|
+ .start();
|
|
|
|
|
+ inherit.getOutputStream().close();
|
|
|
|
|
+ inherit.waitFor();
|
|
|
|
|
+
|
|
|
|
|
+ // DISCARD:子进程输出被丢弃,父进程读不到任何内容
|
|
|
|
|
+ log.info("== Redirect.DISCARD(输出被丢弃) ==");
|
|
|
|
|
+ Process discard = new ProcessBuilder(Jdk.java(), "-Dfile.encoding=UTF-8", "Main")
|
|
|
|
|
+ .directory(WORK_DIR)
|
|
|
|
|
+ .redirectOutput(ProcessBuilder.Redirect.DISCARD)
|
|
|
|
|
+ .redirectError(ProcessBuilder.Redirect.DISCARD)
|
|
|
|
|
+ .start();
|
|
|
|
|
+ discard.getOutputStream().close();
|
|
|
|
|
+ discard.waitFor();
|
|
|
|
|
+ log.info("DISCARD 结束,退出码: {}", discard.exitValue());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** 起一个后台线程把给定输入流按行读取并打日志,读到 EOF(子进程退出)后结束 */
|
|
|
|
|
+ private static Thread drain(String tag, InputStream in) {
|
|
|
|
|
+ Thread thread = new Thread(
|
|
|
|
|
+ () -> new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
|
|
|
|
|
+ .lines()
|
|
|
|
|
+ .forEach(line -> log.info("[{}] {}", tag, line)),
|
|
|
|
|
+ "drain-" + tag);
|
|
|
|
|
+ thread.setDaemon(true);
|
|
|
|
|
+ thread.start();
|
|
|
|
|
+ return thread;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|