浏览代码

示例二补:重定向案例点(相互重定向/重定向到文件/Redirect枚举)

yangyi 1 周之前
父节点
当前提交
a67a588f96
共有 1 个文件被更改,包括 145 次插入0 次删除
  1. 145 0
      src/main/java/space/anyi/process/ProcessRedirectExample.java

+ 145 - 0
src/main/java/space/anyi/process/ProcessRedirectExample.java

@@ -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;
+    }
+}