【问题标题】:Using command prompt with Java在 Java 中使用命令提示符
【发布时间】:2016-04-18 04:12:43
【问题描述】:

我正在使用 Windows 7 机器。

我正在开发一个应用程序,它是 Haskell 的 GHCi 解释器的前端。用户输入一个命令,Java 将通过 Runtime 上的 exec() 方法执行该命令,然后应用程序将显示用户使用命令提示符运行 GHCi 时会显示的文本。

现在,我遇到了打印输出的循环问题。

这是我现在拥有的代码。

public class GHCiTest {
public static Scanner rd, sc;

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {

try {

  System.out.println("Starting... ");

  Process p = Runtime.getRuntime().exec("ghci");

  PrintStream hugsin  = new PrintStream(p.getOutputStream());
  InputStream hugsout = p.getInputStream();

  sc = new Scanner(hugsout);
  rd = new Scanner(System.in);

  String rdnextline;



  while (true){
    while (sc.hasNextLine()){
        System.out.println(sc.nextLine());

    }
      System.out.println("yay");
      rdnextline = rd.nextLine();
    if (rdnextline == "quit"){break;}
    hugsin.println(rdnextline);
    hugsin.flush();

  }
  System.out.println(" ... successful completion.");
}
catch(IOException e) {
  e.printStackTrace();
}
}

}

我知道 GHCi 的初始启动是有效的,因为程序正在打印“GHCi,版本 7.10.3:http://www.haskell.org/ghc/:? for help”。但是,问题似乎是 while(sc.hasNextLine()) 循环,它应该读取命令提示符的输出并将其输出,直到没有任何剩余,因为它不会跳出循环并继续读取用户输入。我知道这一点是因为程序没有打印我在循环后放入的“yay”标志。

【问题讨论】:

  • 您是否尝试过使用缓冲阅读器而不是扫描仪?
  • 因为sc.hasNextLine() 一直等到到达流结束(如果没有输入)...并且流结束只会在程序退出时发生。
  • 两个问题: 1. GHCi 并不是真正设计成这样使用的。它是一个交互式开发工具。依赖其交互式用户界面的任何细节都可能使您的程序在新版本出现时中断。你可以使用ghc -e 来达到你的目的吗? 2. 你最好不要将 GHCi 暴露在网络上,除非你周围有一些主要的沙箱;完整的 GHCi 访问权限就像完整的 shell 访问权限。

标签: java haskell ghci


【解决方案1】:

像这样在另一个线程中接收 ghci 的输出。

System.out.println("Starting... ");

Process p = Runtime.getRuntime().exec("ghci");

PrintStream hugsin = new PrintStream(p.getOutputStream());
InputStream hugsout = p.getInputStream();
Scanner rd = new Scanner(System.in);
new Thread(() -> {
    try (Reader r = new InputStreamReader(hugsout)) {
        int ch;
        while ((ch = r.read()) != -1)
            System.out.print((char)ch);
    } catch (IOException e ) {}
}).start();
Scanner sc = new Scanner(hugsout);
String rdnextline;
while (true) {
    rdnextline = rd.nextLine();
    hugsin.println(rdnextline);
    hugsin.flush();
    if (rdnextline.equals("quit")) {
        break;
    }
}
System.out.println(" ... successful completion.");

【讨论】:

  • 谢谢你,它似乎正在工作。看来您需要一个阅读器并让它读取字符并一次打印出一个。你为什么要检查字符是否不是-1?
  • 为什么 while(true) 循环只能不断地检索用户输入?看起来程序只打印了一次 GHCi 的输出,但它循环得很好。
【解决方案2】:

在到达流的末尾之前,您的循环不会退出:

while (sc.hasNextLine()){
    System.out.println(sc.nextLine());
}

流的结束就是你的进程的结束。因此,您的 Java 程序正在等待子进程运行完成并终止。一旦发生这种情况,循环将结束,Java 程序将向进程发送所需的命令。

对不起,我的意思是,尝试向进程发送所需的命令;它不会成功,因为进程已终止。

如果 GHCi 进程输出某种“提示”,您可以尝试在那个时候破坏您的 while(...) { print },从用户那里获取输入,将其发送到进程,然后循环返回并重新输入您的while(...) { print },等待下一个提示。

假设提示不以换行符结束,而是出现在用户输入被键入的行的开头,您不能使用while(sc.hasNextLine()) { ... } 类型的循环,因为提示不是完整的行。您可能不得不求助于逐个字符地阅读,在最后“n”个字符中查找提示序列。

看起来您可以更改 GHCi 中的提示。见here for details。如果您将提示更改为以换行符结尾,您仍然可以按行读取流。

while (sc.hasNextLine()){
    String line = sc.nextLine();
    if (line.equals("YourPromptHere"))
         break;
    System.out.println(line);
}

(或者,您可以使用线程做一些事情,以允许两个部分运行而不会相互阻塞。当然,线程有其自身的问题和复杂性。)


编辑

我眼前一亮,眼前一亮。假设 GHC 的提示看起来像这样......

GHCi, version 7.10.3
yada, yada, yada ...
Main> _

...您可以将扫描仪的分隔符设置为提示字符串Main>

// Set scanner delimiter to GHCi's Prompt string
sc = new Scanner(hugsout).setDelimiter("^Main> ");

while (sc.hasNext()) {

    // Echo GHCi's output upto the delimiter (prompt)
    System.out.println(sc.next());

    // Read user input & transfer to GHCi.
    System.out.print("Replacement Prompt> ");
    rdnextline = rd.nextLine();
    if (rdnextline == "quit") {
        break;
    }
    hugsin.println(rdnextline);
    hugsin.flush();
 }

注意:这不考虑辅助提示,当 GHCi 需要更多输入来完成命令时使用。您可以使用类似于"^Main> |\bAlt> " 之类的正则表达式来匹配任一提示,但您将无法判断分隔符匹配哪个提示。 第一个子表达式"^Main> " 匹配行首,后跟“Main>”,而第二个子表达式"\bAlt> " 只匹配单词边界,后跟“Alt>”。这是因为 GHCi 的输出流看起来像 "\nMain> Alt> ",在 Alt> 之前有很长的停顿; Alt> 之前的“换行符”通常来自输入流上按下的 Enter 键的回显。

【讨论】:

  • 你知道我怎样才能让它打印出所有内容,直到用户必须输入输入?
  • 观察输出流,寻找来自GHCi的提示。见编辑
  • 你是说收到新行后,我应该检查它是否等于“Main>”,这是 GHCi 在请求用户输入之前打印的内容?是否有一个通用的解决方案来确定扫描仪是否被阻止读取 InputStream 中的下一个字符?
  • 如果您可以将提示更改为“Main>\n”,(注意末尾的换行符),那么可以。 in.nextLine() 将返回整个提示(减去换行符),这表明 GHCi 将期待用户输入。有没有通用的解决方案?不会。 Haskell 程序可能需要很长时间才能产生输出;扫描仪是否被阻塞?或者,Haskell 程序可能期待用户的输入。如果您在最后一个字符出现在输出上之后等待(例如)10 秒,这可能表明它正在等待输入......或者它可能只是很慢。
  • 显而易见的闪光。不要试图强制 GHCi 的提示符匹配 nextLine() 分隔符 \n,而是更改扫描仪的分隔符以匹配 GHCi 的提示符。见编辑。