【问题标题】:Running interactive shell program in Java在 Java 中运行交互式 shell 程序
【发布时间】:2015-04-19 17:06:16
【问题描述】:

我正在尝试(但失败了)找出如何在 Java 中运行完全交互式的 shell 程序。

这是场景:

我有一个用 Java 编写的跨平台大型 GUI 应用程序。为此,我正在尝试添加一个交互式命令行环境以无头运行。事情的那一面都很好,花花公子。然而,主 GUI 的功能之一是编辑文件。现在,对于命令行界面,我希望能够执行外部编辑器来编辑文件,然后在保存并退出后返回到我所在的位置。例如,在 Linux 上它可能会执行“vi /path/to/file”。

那么我怎样才能以键盘和显示器与应用程序完全交互的方式执行该命令呢?我不希望它在后台运行。我不希望它通过 Java 重定向 IO,我只希望一个命令“在前台”运行直到它存在。

就像我在 C 中使用 system() 函数一样。

到目前为止,我发现的所有内容都在后台运行命令或通过 Java 管道传输 IO,这不适用于交互式(非行模式)应用程序。

哦,还有最后一个警告:我受限于 Java 1.6 兼容性,所以我不能用 ProcessBuilder 做一些花哨的事情。

这是强制性的 SSCCE:

class exetest {
    public static void main(String[] args) { 
        try {
            Process p = Runtime.getRuntime().exec("vi");
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

我希望 Runtime.getRuntime().exec() 在我完成 vi 之前一直阻塞,并且在此期间所有键盘输入都会直接(众所周知的RAW)一直到 vi并且所有屏幕输出都会直接返回给用户,不会被触及。

这就是我在 C 中实现它的方式:

void main() {
    system("vi");
}

更新:

这达到了预期的结果,但是 a) 仅限于 Linux / OSX(不是什么大问题,但如果能跨平台就好了),并且 b) 是一个糟糕的组合:

import java.io.*;

class exetest {
    public static void main(String[] args) {
        try {
            Process p = Runtime.getRuntime().exec("/bin/bash");
            OutputStream stdin = p.getOutputStream();
            PrintWriter pw = new PrintWriter(stdin);
            pw.println("vi < /dev/tty > /dev/tty");
            pw.close();
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

【问题讨论】:

  • 你想要github.com/ronniedong/Expect-for-Java这样的东西吗?
  • 不,我不知道。我不希望 Java 与执行的程序交互,我希望用户与之交互。所以他们输入edit myfile,它执行vi /path/to/myfile,用户编辑文件就像他们在终端输入vi /path/to/myfile一样。

标签: java exec


【解决方案1】:

system("vi")确切等效项是:

final ProcessBuilder p = new ProcessBuilder("vi");
p.redirectInput(Redirect.INHERIT);
p.redirectOutput(Redirect.INHERIT);
p.redirectError(Redirect.INHERIT);

p.start().waitFor();

(我知道这延迟了 3 年,但对于其他有同样问题的人)

【讨论】:

  • 您也可以将三个redirect* 调用替换为单个p.inheritIO() 调用。另请注意,这些方法仅在 JDK 1.7+ 中可用。
  • system 也会做其他事情,比如忽略 SIGINT 以便 ^C 杀死孩子而不是父母。 (这是需要这种行为的极少数情况之一。)
【解决方案2】:

我提前声明这是一个糟糕的组合,但考虑到你的限制,我不确定你会找到更优雅的东西。

此外,它不适用于 Windows。

使用 bash:

class Exetest {
    public static void main(String[] args) { 
        try {
            // Create a one-liner for bash
            // Note that the trick is to do all the redirects and
            // give the name of the file in a single argument.
            String [] commandline = new String[3];
            commandline[0] = "bash";
            commandline[1] = "-c";
            commandline[2] = "vi </dev/tty >/dev/tty test.txt";
            Process p = Runtime.getRuntime().exec(commandline);
            p.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通过这种方式,您可以在运行时制作您的实际命令。

【讨论】:

  • 你是对的,这是一个可怕的组合;)是的,它达到了预期的结果,但我不确定我是否能接受这种实现。编辑器可执行文件将在运行时决定,我不确定您是否可以在一行中制作一个包含重定向的 bash 命令,因此它必须动态生成一个脚本才能执行。不好。
  • @Majenko 我编辑给你一个基于 bash 的内衬,可以在运行时制作命令。
  • 我之前尝试过 - 重定向不起作用。您必须像我上面的编辑一样通过标准输入发送它。
  • @Majenko,相信我,我在粘贴之前进行了测试。在 Mac 和 Linux 上,这都有效。你确定你包含了-c 并且使用了一个命令数组而不是一个字符串?
  • 我实际上可能把它作为一个字符串而不是一个数组来完成。标准输入仍然可以工作并且稍微安全一些(虽然不多)。
【解决方案3】:

首先,ProcessBuilder 也在 Java 6 中。如果您的意思是该版本不足或其他什么,那么我认为您需要了解两条信息。首先,运行“外部编辑器”的主要方式可能是使用 Runtime 类和 exec 方法。我怀疑这就是你已经在做什么了?其次,在某种程度上,您当前的 GUI 有一个“事件循环”,它是一个简单但主要的线程,除了“等待事件”(例如按键或“窗口事件”)之外什么都不做,调度该事件感兴趣的听众,然后等待下一个事件。外部编辑器的按键和事件不会通过这个事件循环......好吧,不容易,无论如何,如果不使用更复杂的东西,比如 OLE API。但是,正是“事件循环”使 Java 保持活力和交互性,同时其他事情也继续进行,例如在外部编辑器中进行编辑。我不确定您使用的是什么 GUI 框架,但大多数人已经有了这样的循环,您不必担心它。如果你说在这个命令行模式下你想要,你不希望你的普通 GUI 运行,那么你基本上必须复制已有的“事件循环”,并首先启动该线程,然后调用 runtime.exec方法。有道理?

FWIW,如果您没有“源”来查看您当前使用的 GUI 框架,如果您想查看一个,只是为了了解有关事件循环的更多信息,Eclipse SWT 在它的“显示”类中有一个以及一些可能与您想要做的类似的示例。 (注意,SWT 可以在没有 Eclipse 的其余部分的情况下“独立”运行,以防您想知道)。

HTH

【讨论】:

  • 在 CLI 模式下运行时没有 GUI。 GUI 代码甚至还没有开始。当它发生时,一切都在摇摆。没有 GUI 就没有 GUI 事件循环。我实际上将 JLine 用于 CLI 界面。
  • 6中的ProcessBuilder没有Redirect类。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-07-11
  • 1970-01-01
  • 1970-01-01
  • 2018-10-30
  • 2014-12-28
相关资源
最近更新 更多