Java读取Linux Shell脚本stdout无效,请求错误排查方法
这个问题我之前也碰到过,核心原因出在你代码里的processBuilder.inheritIO();配置上,咱们一步步拆解排查和解决:
问题根源分析
你设置了inheritIO(),这个方法的作用是让子进程(也就是你启动的bash脚本)直接继承当前Java进程的标准输入、输出和错误流。也就是说,脚本里的echo "I am waiting"会直接输出到Java进程的控制台,而不会进入process.getInputStream()对应的流里——这就是为什么你能看到I am waiting,但BufferedReader却读不到内容的原因。
具体排查步骤
先验证IO配置的影响
直接注释掉processBuilder.inheritIO();这行代码,重新编译运行。这时候子进程的stdout会被Java进程的InputStream捕获,你的BufferedReader应该就能读到内容,输出--> I am waiting了。处理sudo的潜在问题
你用了sudo执行脚本,如果当前用户没有配置免密sudo,进程可能会卡在等待输入密码的状态,导致看起来没有输出。可以通过修改/etc/sudoers文件(用visudo命令编辑更安全),添加类似如下配置,让当前用户免密执行该脚本:your_username ALL=(ALL) NOPASSWD: /home/test.sh替换
your_username为实际的用户名,确保路径和脚本一致。别忽略错误流的阻塞
即使你解决了stdout的问题,如果子进程有错误输出(比如sudo权限问题、脚本路径错误),错误流(stderr)如果没有被读取,子进程可能会因为缓冲区满而阻塞,无法正常执行。所以最好同时读取stdout和stderr,避免这种情况。
修改后的可用代码示例
这里给你调整后的代码,同时处理stdout和stderr,避免阻塞:
import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class ShellScriptExecutorTest { public static void main(String[] args) { try { ProcessBuilder processBuilder = new ProcessBuilder("sudo", "bash", "./test.sh"); processBuilder.directory(new File("/home/")); // 移除inheritIO,让Java进程捕获子进程的流 // processBuilder.inheritIO(); Process process = processBuilder.start(); // 单独线程读取stdout,避免阻塞主线程 new Thread(() -> { try (InputStream is = process.getInputStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr)) { String line; while ((line = br.readLine()) != null) { System.out.println("--> " + line); } } catch (IOException e) { e.printStackTrace(); } }).start(); // 单独线程读取stderr,防止子进程因缓冲区满阻塞 new Thread(() -> { try (InputStream is = process.getErrorStream(); InputStreamReader isr = new InputStreamReader(is); BufferedReader br = new BufferedReader(isr)) { String line; while ((line = br.readLine()) != null) { System.err.println("ERR --> " + line); } } catch (IOException e) { e.printStackTrace(); } }).start(); int exitValue = process.waitFor(); System.err.println("exitValue: " + exitValue); } catch (InterruptedException | IOException e) { e.printStackTrace(); } } }
额外注意事项
- 用
try-with-resources语法(就是代码里的try (InputStream is = ...))可以自动关闭流,避免资源泄漏。 - 如果你的脚本有长时间运行或者大量输出的情况,用线程读取流是更稳妥的方式,不会阻塞主线程的执行。
内容的提问来源于stack exchange,提问作者mrbela

