Runtime.getRuntime().exec(“C:\cygwin\bin\bash.exe”) 没有可读取的输入

发布于 2024-09-16 23:49:15 字数 3205 浏览 3 评论 0原文

我正在尝试执行一个新进程并从 Java 的输入流中读取数据。我已成功使用 Runtime.getRuntime().exec(String) 启动并接收来自多个进程的输入。但是,当我尝试在其他一些进程上使用 exec 时,输入流的 read 方法会阻塞,并且似乎没有输入。对于某些进程来说,什么可能导致输入流为空?具体来说,我想知道为什么 bash.exe 不输出任何内容。

我编写了一个 JUnit 测试用例来演示这个问题:

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import junit.framework.TestCase;

public class TestExec extends TestCase {

    public void testExec() throws IOException {
        List<InputPrinter> threads = new ArrayList<InputPrinter>();

        // Create a process for each of the commands and make sure that
        // it outputs at least one line to its input stream.
        threads.add(testExec("cmd"));
        threads.add(testExec("java"));
        threads.add(testExec("C:/cygwin/bin/vim-nox.exe"));

        // These bottom two fail, even though executing these
        // commands in cmd.exe results in immediate output
        threads.add(testExec("javac"));
        threads.add(testExec("C:/cygwin/bin/bash.exe"));

        // Give the threads a second to execute
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            fail();
        }

        // Test that each command had input to read
        for(InputPrinter ip : threads) {
            assertTrue(ip.command + " has not read any input", ip.hasRead);
        }
    }

    // Starts a process for the given command and returns an
    // InputPrinter that can be used to check if the process
    // has had an input to read.
    public InputPrinter testExec(String command) throws IOException {
        Process proc = Runtime.getRuntime().exec(command);
        InputStream in = proc.getInputStream();

        InputPrinter ip = new InputPrinter(in, command);
        new Thread(ip).start();

        return ip;
    }

    // Simple Runnable to read from an InputStream. hasRead will be
    // true if at least one input has been read from the stream
    private class InputPrinter implements Runnable {
        InputStream in;
        String command;
        boolean hasRead;

        public InputPrinter(InputStream in, String command) {
            this.in = in;
            this.command = command;
            this.hasRead = false;
        }

        // Loop indefinitely while printing any received input
        public void run() {
            try {
                final byte[] b = new byte[1024];
                while (true) {
                    int n = in.read(b);
                    if (n > 0) {
                        System.out.print(new String(Arrays.copyOf(b, n)));
                        hasRead = true;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                fail();
            }
        }
    }

}

编辑:

据我所知,如果程序没有使用 stdout 或 stderr,我不应该在 Windows 命令提示符中看到任何内容。当我启动 bash 进程时,我期望看到的是“bash-3.2$”,这与我打开命令提示符并运行“bash.exe”时看到的内容相同:

Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\cygwin\bin>bash.exe
bash-3.2$

I'm trying to execute a new process and read from its input stream in Java. I have successfully used Runtime.getRuntime().exec(String) to start and receive input from several processes. However, when I try to use exec on some other processes, the input stream's read method blocks, and it appears that there is no input. What might be causing the input stream to be empty for some of these processes? Specifically, I am wondering why bash.exe is not outputting anything.

I have written a JUnit test case to demonstrate this issue:

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import junit.framework.TestCase;

public class TestExec extends TestCase {

    public void testExec() throws IOException {
        List<InputPrinter> threads = new ArrayList<InputPrinter>();

        // Create a process for each of the commands and make sure that
        // it outputs at least one line to its input stream.
        threads.add(testExec("cmd"));
        threads.add(testExec("java"));
        threads.add(testExec("C:/cygwin/bin/vim-nox.exe"));

        // These bottom two fail, even though executing these
        // commands in cmd.exe results in immediate output
        threads.add(testExec("javac"));
        threads.add(testExec("C:/cygwin/bin/bash.exe"));

        // Give the threads a second to execute
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
            fail();
        }

        // Test that each command had input to read
        for(InputPrinter ip : threads) {
            assertTrue(ip.command + " has not read any input", ip.hasRead);
        }
    }

    // Starts a process for the given command and returns an
    // InputPrinter that can be used to check if the process
    // has had an input to read.
    public InputPrinter testExec(String command) throws IOException {
        Process proc = Runtime.getRuntime().exec(command);
        InputStream in = proc.getInputStream();

        InputPrinter ip = new InputPrinter(in, command);
        new Thread(ip).start();

        return ip;
    }

    // Simple Runnable to read from an InputStream. hasRead will be
    // true if at least one input has been read from the stream
    private class InputPrinter implements Runnable {
        InputStream in;
        String command;
        boolean hasRead;

        public InputPrinter(InputStream in, String command) {
            this.in = in;
            this.command = command;
            this.hasRead = false;
        }

        // Loop indefinitely while printing any received input
        public void run() {
            try {
                final byte[] b = new byte[1024];
                while (true) {
                    int n = in.read(b);
                    if (n > 0) {
                        System.out.print(new String(Arrays.copyOf(b, n)));
                        hasRead = true;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
                fail();
            }
        }
    }

}

EDIT:

As far as I know, if a program isn't using stdout or stderr, I shouldn't see anything in the windows command prompt. What I am expecting to see when I start the bash process is "bash-3.2$," the same thing I see when I open the command prompt and run "bash.exe":

Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation.  All rights reserved.

C:\cygwin\bin>bash.exe
bash-3.2$

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

ゞ花落谁相伴 2024-09-23 23:49:15

无论Java如何,据我所知,只有当它作为脚本运行时,才能通过管道从/向bash传输输出(或输入),而不是当它作为交互式shell运行时(在这种情况下,你只能将cmd参数传递给它)。

换句话说,当您像评论中提到的那样从 cmd 运行 bash 时,您会看到输出,但它包含在 bash 进程中,而不是 bash 发送回父 cmd 进程的输出。

关于javac进程,它实际上是将输出发送到错误流。尝试从 cmd javac 1>nulljavac 2>null 运行,您将看到差异。
您查看过 api 这里< /a>?您可以尝试使用 ProcessBuilder 并将错误流重定向回主输入流,这样处理流程会更容易。

Regardless of Java, as far as I know you can pipe output (or input) from/to bash only when it is running as a script, not when it is running as an interactive shell (in which case you can only pass cmd parameters to it).

In other words, when you run bash from cmd as you mention in the comment, you see output, but it is contained in the bash process, it is not output that bash sends back to the parent cmd process.

Regarding the javac process, it is actually sending the output to the error stream. Try running from cmd javac 1>null and javac 2>null and you'll see the difference.
Have you looked at the api here? You can try to use ProcessBuilder and redirect the error stream back to the primary input stream, it'll be much easier to work with the processes this way.

心碎的声音 2024-09-23 23:49:15

一个进程通常不仅有一个,而且有两个与其关联的输出流。它们是:

  1. stdout,可以使用 getInputStream() 读取
  2. stderr,可以使用 getErrorStream() 读取

Javac 写入 stderr,而不是 stdout,因此您不会读取其输出。

因为同时读取它们很不方便(几年前,我不得不为此编写一个额外的线程),所以他们向系统进程引入了一个新的 API,即 ProcessBuilder,它允许将 stderr 重定向到 stdout。

只需将这些行替换

    Process proc = Runtime.getRuntime().exec(command);
    InputStream in = proc.getInputStream();

    ProcessBuilder pb = new ProcessBuilder(command);
    pb.redirectErrorStream(true);
    Process proc = pb.start();

,添加所需的导入,您的测试就会成功:)。

A process typically has not only one but two output streams associated with it. These are:

  1. stdout, which can be read with getInputStream()
  2. stderr, which can be read with getErrorStream()

Javac writes to stderr, not stdout, so you don't read its output.

Because it is inconvenient to have to read both of them (Some years ago, I had to write an extra thread for this), they introduced a new API to system processes, namely the ProcessBuilder, which allows to redirect stderr to stdout.

Just replace the lines

    Process proc = Runtime.getRuntime().exec(command);
    InputStream in = proc.getInputStream();

with

    ProcessBuilder pb = new ProcessBuilder(command);
    pb.redirectErrorStream(true);
    Process proc = pb.start();

, add the required imports, and your test succeeds :).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文