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

Posted

技术标签:

【中文标题】从批处理文件的进程中读取 InputStream 会跳到下一行【英文标题】:Reading InputStream from a batch file's process skips to next line 【发布时间】:2012-03-04 07:57:33 【问题描述】:

我正在尝试使用 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) 


我知道我的代码很乱,而且我确信它包含语法错误。

感谢您提供的任何帮助!

【问题讨论】:

【参考方案1】:

您正在创建一个Process,但您没有从其标准错误流中读取。该进程可能正在向其标准错误写入消息以告诉您存在问题,但如果您没有读取其标准错误,您将无法读取这些消息。

你有两个选择:

    由于您已经有一个从流中读取的类 (StreamReader),请将其中的另一个连接到进程的标准错误流 (p.getErrorStream()) 并在另一个 Thread 中运行它。当对p.waitFor() 的调用返回时,您还需要在错误StreamReader 上调用setComplete,并等待运行它的线程终止。

    将您对Runtime.getRuntime().exec() 的使用替换为ProcessBuilder。这个类是 Java 5 中的新类,它提供了一种运行外部进程的替代方法。在我看来,它对Runtime.getRuntime().exec() 最显着的改进是能够将进程的标准错误重定向到其标准输出,因此您只有一个流可供读取。

我强烈建议选择第二个选项并选择将进程的标准错误重定向到其标准输出中。

我拿走了你的代码并替换了行

        final Process p = Runtime.getRuntime().exec(script);

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

另外,我手头没有你的 GUI 代码,所以我把进程的输出写到了System.out

当我运行你的代码时,我得到了以下输出:

你好。等待 5 秒。 错误:不支持输入重定向,立即退出进程。 完成的。再见。 处理完成,退出代码 0

如果您看到该错误消息,您可能已经知道timeout 命令出现了问题。

顺便说一句,我在您的一个 cmets 中注意到 ughzan 建议的命令都不起作用。我用ping -n 5 127.0.0.1 > NUL 替换了timeout 行,脚本按预期运行。我无法重现此问题。

【讨论】:

哦!我曾尝试创建另一个读取进程错误流的 StreamReader,但我收到了相同的消息!我只是查看了该消息,并认为这意味着您永远无法从批处理文件的错误流中读取,而不是从超时中读取……我想这更有意义。谢谢帮忙。但是,我仍然不知道最终的解决方案。我在批处理文件中主要做的是使用 adb shell 在连接的 android 设备上运行某些命令...如果问题出在命令中,那么我该怎么做才能解决问题? 另外,关于 ping 工作/不工作......对不起,那是我的错误。我已经解决这个问题几天了,一旦我将批处理文件更改为超时和 ping 的东西,它就跳过了超时,但我记错了,并认为 ping 也提前终止了......对此感到抱歉。 在编写用于启动控制台应用程序并将其输出重定向到我的应用程序的代码时,我正在寻找解决我的 Delphi 代码中相同错误的方法。我正在测试的批处理文件也有一个 TIMEOUT /T 调用,它给出了同样的错误。问题出在调用 CreateProcess 的代码中 - 您必须确保 STARTUP_INFO 的 hStdInput 为 0。在我的情况下,这是管道的读取句柄。我不了解 Java,这个问题很可能出在您正在使用的底层类中,所以怀疑这是否有很大帮助,但是 FWIW。【参考方案2】:

问题肯定出在timeout.exe。如果你在timeout 后面加上echo %errorlevel%,你会看到如果从java 运行它返回1。和0 如果以通常的方式运行。可能,它需要一些特定的控制台功能(即光标定位),这些功能在从 java 进程运行时会被抑制。

在从 Java 运行时,我可以做些什么来让它工作

如果您不需要运行任何批处理文件的能力,请考虑将timeout 替换为ping。否则...我尝试使用JNA 槽Kernel32.CreateProcesstimeout 运行批处理文件运行良好。但是,您还需要通过本机调用来实现进程输出的读取。

我希望有人能提出更好的方法。

【讨论】:

感谢您的提示。你是对的;错误级别在 java 中为 1,但在命令提示符下为 0。在从 Java 运行时,我能做些什么来让它工作吗?【参考方案3】:

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");
    

【讨论】:

【参考方案4】:

这可能是因为您的“超时...”命令返回错误。

三种测试方法:

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

【讨论】:

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

以上是关于从批处理文件的进程中读取 InputStream 会跳到下一行的主要内容,如果未能解决你的问题,请参考以下文章

Java InputStream 无法从 JAR 中读取文件

如何以编程方式取消从 InputStream 中读取?

是否可以从 InputStream 创建一个 File 对象

如何从不同于网络的本地文件中读取 InputStream 对象(通过 Amazon S3)?

Java:InputStream 读取大文件太慢

将文件从外部存储加载到 Inputstream