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命令,彻底避免密码输入问题:
- 执行
sudo visudo打开sudo配置文件 - 添加一行:
你的用户名 ALL=(ALL) NOPASSWD: /bin/cp - 保存退出后,执行
sudo cp时就无需输入密码了
内容的提问来源于stack exchange,提问作者max23
相关产品推荐
相关产品推荐

