You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java ProcessBuilder实现sudo密码输入终端不可见的方案

问题描述

我需要将Docker卷中的文件复制到主机,通过Java的ProcessBuilder调用sudo命令时,输入的密码会在终端明文显示,推测该问题由pb.inheritIO();导致。我希望实现类似Ubuntu终端输入密码时不可见的效果,但去掉pb.inheritIO();后程序无法正常终止,请问该如何解决?

原始代码

// Process Builder
ProcessBuilder pb = new ProcessBuilder();
String cmd = "cp";
String tableName = "password_entries.ibd";
String source = "/var/lib/docker/volumes/d74a425c8728e5333a3472860c2b62a3e47a0b5655dd44cce1b4c47ac2c3b6b8/_data/password_safe/";
String target = ".";

try {
    pb.inheritIO();
    // cp to host
    pb.command("sudo", "-S", cmd, source + tableName, target);
    pb.start().waitFor();
} catch (IOException | InterruptedException e) {
    throw new RuntimeException(e);
}

修改后的代码

ProcessBuilder pb = new ProcessBuilder();
String cmd = "cp";
String tableName = "password_entries.ibd";
String source = "/var/lib/docker/volumes/d74a425c8728e5333a3472860c2b62a3e47a0b5655dd44cce1b4c47ac2c3b6b8/_data/password_safe/";
String target = ".";

try {
     //pb.inheritIO();
     Process p;

     // cp to host
     pb.command("sudo", "-S", cmd, source + tableName, target);
     p = pb.start();
     OutputStream os = p.getOutputStream();
     PrintWriter writer = new PrintWriter(os, true);
     writer.write("test\n");
     System.out.println(p.waitFor());
} catch (IOException | InterruptedException e) {
     throw new RuntimeException(e);
}
解决方案

1. 手动处理流+安全读取密码

去掉inheritIO()后进程阻塞,是因为sudo的输出/错误流未被读取,导致缓冲区满卡主进程。下面的代码既实现密码输入不可见,又能保证进程正常终止:

ProcessBuilder pb = new ProcessBuilder();
String cmd = "cp";
String tableName = "password_entries.ibd";
String source = "/var/lib/docker/volumes/d74a425c8728e5333a3472860c2b62a3e47a0b5655dd44cce1b4c47ac2c3b6b8/_data/password_safe/";
String target = ".";

try {
    pb.command("sudo", "-S", cmd, source + tableName, target);
    // 将错误流重定向到输出流,统一处理
    pb.redirectErrorStream(true);
    Process p = pb.start();

    // 开启线程读取子进程输出,避免阻塞
    new Thread(() -> {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // 识别sudo的密码提示并打印
                if (line.contains("[sudo] password")) {
                    System.out.print(line + ": ");
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();

    // 用系统控制台安全读取密码(无明文回显)
    Console console = System.console();
    if (console != null) {
        char[] passwordChars = console.readPassword();
        String password = new String(passwordChars);
        try (PrintWriter writer = new PrintWriter(p.getOutputStream(), true)) {
            writer.println(password);
        }
        // 清空密码数组,避免内存残留
        Arrays.fill(passwordChars, ' ');
    } else {
        System.err.println("无法获取控制台,无法安全输入密码");
        p.destroy();
        return;
    }

    int exitCode = p.waitFor();
    System.out.println("\n命令执行完成,退出码:" + exitCode);
} catch (IOException | InterruptedException | ArrayIndexOutOfBoundsException e) {
    throw new RuntimeException(e);
}

2. 核心逻辑说明

  • 密码无回显:使用System.console().readPassword(),这是Java原生的安全读取密码方式,输入时终端不会显示明文。
  • 避免进程阻塞:必须单独读取子进程的输出流,否则sudo的提示信息会填满缓冲区,导致进程挂起。
  • 清理敏感数据:读取密码后清空字符数组,避免密码在内存中残留。

3. 更省心的方案:配置sudo免密

如果是长期运行的程序,推荐直接给当前用户配置sudo免密执行cp命令,彻底避免密码输入问题:

  1. 执行sudo visudo打开sudo配置文件
  2. 添加一行:你的用户名 ALL=(ALL) NOPASSWD: /bin/cp
  3. 保存退出后,执行sudo cp时就无需输入密码了

内容的提问来源于stack exchange,提问作者max23

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 20:13:12