从批处理文件的进程中读取InputStream会跳到下一行

3

我试图使用Runtime.exec()运行一个批处理文件,并将其InputStream输出到JTextArea中。我的方法有所作为,但只有部分成功。发生的情况是批处理文件运行,但如果它执行了一个除“echo”之外的命令,则该命令立即终止并执行下一行。例如,假设我尝试运行这样一个简单的批处理文件:

@echo off
echo hello. waiting 5 seconds.
timeout /t 5 /nobreak > NUL
echo finished. goodbye.

批处理文件正在执行,JTextArea 显示:
hello. waiting 5 seconds.
finished. goodbye.

但是它在中途没有等待5秒钟。我无法弄清楚为什么会这样。下面是我用来运行批处理文件并读取其InputStream的代码。
private class ScriptRunner implements Runnable {
private final GUI.InfoGUI gui; // the name of my GUI class
private final String script;

public ScriptRunner(final GUI.InfoGUI gui, final File script) {
    this.gui = gui;
    this.script = script.getAbsolutePath();
}

@Override
public void run() {
    try {
        final Process p = Runtime.getRuntime().exec(script);
        StreamReader output = new StreamReader(p.getInputStream(), gui);
        Thread t = new Thread(output);
        t.start();
        int exit = p.waitFor();
        output.setComplete(true);
        while (t.isAlive()) {
            sleep(500);
        }
        System.out.println("Processed finished with exit code " + exit);
    } catch (final Exception e) {
        e.printStackTrace();
    }
}

}
private class StreamReader implements Runnable {
private final InputStream is;
private final GUI.InfoGUI gui;

private boolean complete = false;

public StreamReader(InputStream is, GUI.InfoGUI gui) {
    this.is = is;
    this.gui = gui;
}

@Override
public void run() {
    BufferedReader in = new BufferedReader(new InputStreamReader(is));
    try {
        while (!complete || in.ready()) {
            while (in.ready()) {
                gui.setTextAreaText(in.readLine() + "\n");
            }
            sleep(250);
        }
    } catch (final Exception e) {
        e.printStackTrace();
    } finally {
            try {
                in.close();
        } catch (final Exception e) {
                e.printStackTrace();
    }
}

public void setComplete(final boolean complete) {
    this.complete = complete;
}

}
public void sleep(final long ms) {
try {
    Thread.sleep(ms);
} catch (final InterruptedException ie) {
}
}

我知道我的代码相当混乱,而且我确定它包含语法错误。

感谢您能够提供帮助!

4个回答

3
您正在创建一个Process,但您没有从其标准错误流中读取。该进程可能会写入消息到其标准错误中,以告知您存在问题,但如果您没有读取其标准错误,您将无法阅读这些消息。
您有两个选择:
  1. 由于您已经有一个读取流的类(StreamReader),因此可以将另一个类连接到进程的标准错误流(p.getErrorStream())并在另一个线程中运行它。当调用p.waitFor()返回时,您还需要在错误StreamReader上调用setComplete,并等待运行它的线程死机。
  2. 使用ProcessBuilder替换您对Runtime.getRuntime().exec()的使用。这个类是Java 5中的新类,提供了运行外部进程的另一种方式。我认为它最重要的改进之一是能够将进程的标准错误重定向到其标准输出,因此您只需要从一个流中读取。
我强烈建议选择第二个选项,并选择将进程的标准错误重定向到其标准输出。
我采用了您的代码并替换了这一行。
        final Process p = Runtime.getRuntime().exec(script);

使用

        final ProcessBuilder pb = new ProcessBuilder(script);
        pb.redirectErrorStream(true);
        final Process p = pb.start();

此外,我没有你的GUI代码,所以我将过程的输出写到了System.out中。 当我运行你的代码时,我得到了以下输出: hello. waiting 5 seconds. ERROR: 输入重定向不受支持,立即退出处理过程。 finished. goodbye. 已完成处理,退出码为0 如果你看到了这个错误信息,你可能会注意到timeout命令出了问题。 顺便说一下,我注意到在你的评论中,ughzan建议的所有命令都无效。 我用ping -n 5 127.0.0.1 > NUL替换了timeout行,脚本按预期运行。我无法再现这个问题。

哦!我曾尝试创建另一个StreamReader来读取进程的错误流,并收到了相同的消息!我刚刚查看了一下消息,认为它意味着你永远不能从批处理文件的错误流中读取,而不是超时...不过我想这更有道理。谢谢你的帮助。然而,我仍然没有最终解决方案。在批处理文件中,我主要使用adb shell在连接的Android设备上运行某些命令...如果问题出现在命令中,那么我该怎么办才能解决问题呢? - user765383
另外,关于 ping 是否正常工作...抱歉,那是我的错误。我已经在解决这个问题几天了,一旦我将批处理文件更改为超时和 ping 两个东西,并且它跳过了超时,但我记忆有误,认为 ping 也会提前终止...对此我深感抱歉。 - user765383
当我编写启动控制台应用程序并将其输出重定向到我的应用程序的代码时,在Delphi代码中遇到了相同错误的解决方案。我正在测试的批处理文件还有一个TIMEOUT / T调用,也会出现相同的错误。问题在于调用CreateProcess的代码 - 您必须确保您的STARTUP_INFO的hStdInput为0。在我的情况下,这是一个管道的读取句柄。我不知道Java,并且这个问题可能存在于您使用的基础类中,因此怀疑这没有太大帮助,但供参考。 - TByte

2
问题明显出现在timeout.exe中。如果在timeout所在的行之后添加echo %errorlevel%,你将会发现如果从Java中运行它会返回1,而以平常方式运行则返回0。可能需要一些特定的控制台功能(例如游标定位),这些功能在从Java进程运行时被禁止了。

有没有什么方法可以让其在从Java中运行时正常工作?

如果你不需要运行任何批处理文件,则考虑用ping代替timeout。否则...我尝试通过Kernel32.CreateProcessJNA运行批处理文件,timeout可以正常运行。但是,你还需要通过本地调用实现读取进程输出。
希望有人能提供更好的解决方案。

谢谢您的建议。您是正确的;从Java获取的errorlevel为1,而从命令提示符获取的为0。我是否有办法在从Java运行时使其正常工作呢? - user765383

1

ready 方法只是告诉您流是否可以立即读取某些内容,而不会阻塞。您不能真正信任它,因为始终返回 false 是一种有效的实现方式。带有缓冲区的流可能仅在有内容缓冲时才返回 true。因此,我怀疑您的问题就出在这里:

    while (!complete || in.ready()) {
        while (in.ready()) {
            gui.setTextAreaText(in.readLine() + "\n");
        }
        sleep(250);
    }

它应该更像是这样:

    String line;
    while (!complete || (line=in.readLine()) != null) {
        gui.setTextAreaText(line + "\n");
    }

1

可能是因为您的“timeout…”命令返回了错误。

测试它的三种方法:

  1. 检查在Windows命令提示符中“timeout…”命令是否有效。
  2. 在脚本中用“ping -n 5 127.0.0.1 > NUL”替换“timeout…”(它基本上做同样的事情)
  3. 从您的脚本中删除除“timeout /t 5 /nobreak > NUL”之外的所有内容。如果超时失败,该进程应返回错误(1),因为它是执行的最后一个命令。

我尝试的所有不同命令仍然会返回错误1。除了运行“cmd /c file.bat”之外,我还能做什么? - user765383

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接