我有一个长时间运行的 Java 服务器应用程序,它启动一个子进程来执行特定任务(在本例中,使用 7z
命令行实用程序提取 7zip 文件的内容,但该特定细节应该'在这里是相关的)。
- 服务器应用程序在 Ubuntu 14 下使用 Java 8 运行。
- 子进程正在通过 Java ProcessBuilder 启动API。
- 子进程访问的文件可能受密码保护。
- 如果文件受密码保护并且没有提供密码作为命令行参数,
7z
程序将尝试向终端显示一条消息,提示输入密码,然后从终端读取密码。 - 此时,子进程挂起并且不会完成,除非我点击
<Enter>
在控制 Java 进程的终端中两次。
潜在的问题似乎是 7z
实用程序使用 getpass显示提示和读取用户输入的系统调用。根据文档:
The getpass() function opens /dev/tty (the controlling terminal of the process), outputs the string prompt, turns off echoing, reads one line (the "password"), restores the terminal state and closes /dev/tty again.
因为这是一个服务器应用程序,所以让子进程阻塞等待来自 Java 终端的输入是 Not Acceptable 。相反,我想要发生的是以编程方式关闭子进程终端上的输入,以便 getpass
调用立即返回,没有输入,子进程退出并返回错误代码。不幸的是,我所有关闭子进程终端输入的尝试都导致了与上述相同的行为:
- 我尝试手动关闭
Process.getOutputStream()
返回的流子流程启动后立即执行。 - 我尝试使用
ProcessBuilder.redirectInput
重定向子进程输入以从空文件和/dev/null
中读取. - 为了更好的衡量,我什至尝试通过
Redirect.INHERIT
到 ProcessBuilder,尽管这似乎不是我想要的。
看起来这应该是可行的,因为我观察到当 7z
时所需的行为使用与守护进程的子进程完全相同的命令行启动 socat
终端输入连接到 /dev/null
的过程,但我不知道如何使用我掌握的工具在 Java 中完成此任务。
最佳答案
为了防止 getpass() 函数打开/dev/tty(进程的控制终端),我们必须安排该实用程序没有控制 tty,这是通过以下方式实现的打电话setsid() .这个 C 程序(我们将其命名为 notty
)进行此调用,然后执行给定的实用程序:
int main(int argc, char *argv[])
{
setsid(); // get rid of the controlling tty
close(0); // preclude reading STDIN as well
execvp(argv[1], argv+1); // execute the given program file
}
我们可以在 Java 应用程序中使用它,方法是将它添加到 ProcessBuilder 命令之前,例如。例如:
ProcessBuilder pb = new ProcessBuilder("notty", "7z", … /*arguments*/);
关于java - 从Java进程访问子进程控制终端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41124827/