从Java调用脚本,收到了SIGPIPE信号。

3

当我从Java进程调用一个shell脚本时,遇到了一些奇怪的行为。

Process p = Runtime.getRuntime().exec("mybashscript.sh");
(new StreamGobblerThread(p.getInputStream())).start();
(new StreamGobblerThread(p.getErrorStream())).start();
p.waitFor();
returnValue = p.exitValue();

StreamGobblerThread仅有一个run()方法,该方法执行以下操作:
while(((inputStream.available>0) { inputStream.skip(available); }

大约20%的时间这个脚本可以工作,但是大多数情况下,它会立即失败并返回141的错误代码。从我在谷歌上找到的信息来看,141是收到SIGPIPE时的返回代码。有什么想法吗?

也许你应该打印出错误,它们可能会给你一个有意义的错误信息。 - Peter Lawrey
SIGPIPE 可能是由于在 StreamGobblerThread 中从 while 循环提前退出引起的。为了检查它,只需使用空的 run()。在循环中添加一些调试打印来查看 available 是什么。尝试读取数据而不是跳过。 - khachik
似乎除了脚本的返回代码之外,没有其他“错误”,因为没有发生Java异常。所有Java似乎只知道脚本的退出代码。 - Will
如果你将启动错误流线程的那一行注释掉,它还会执行相同的操作吗? - Alexis Dufrenoy
我将尝试将错误线程注释掉。这段代码显然已经在生产环境中运行了很长时间而没有问题,但是只要不涉及stderr,它可能就能正常工作。 - Will
我注释掉了streamGobblerThread方法,因为它们没有做任何事情。现在没有问题了!我忘了提到StreamGobbler本身会在跳过所有字节后立即关闭InputStream。但由于可能还有更多的字节进来,它破坏了管道! - Will
4个回答

2

我不确定问题出在哪里,但首先:

while(((inputStream.available>0) { inputStream.skip(available); }

不是有效的。

这是因为inputStream.available()不是阻塞的,所以如果它没有立即要读取的内容,它将不会读取任何内容。

最好使用以下方式:

byte[] buf = new byte[8192];
int next;
try {
    while ((next = in.read(buf)) != -1) {}
} catch (IOException e) {
    throw new GroovyRuntimeException("exception while dumping process stream", e);
}

read()方法是阻塞的,所以这种方式实际上会一直读取,直到流被正确关闭。

(注意:此代码来自Groovy对consumeProcessOutput()的实现)


不错的发现。StreamGobbler线程实际上在没有更多可用数据时立即关闭了inputStream。但是由于shell脚本可能仍在运行,因此它会过早地关闭它。 - Will

2

我在生活中看到过这种情况,发现了两种解决方法。

  1. 如果你真的想要分别读取错误和输出流,可以运行命令"/bin/sh foo.sh 1>/tmp/out 2>/tmp/err",然后从这些文件中读取。
  2. 如果你可以阅读stdout和stderr的混合内容,则可以使用以下ProcessBuilder:

ProcessBuilder b = new ProcessBuilder("foo.sh"); b.redirectErrorStream(true); Process p = b.start(); p.getInputStream(); //..... etc.

现在从包含stdout和stderr的输入流中读取。


1
这可能意味着发生了“断管”错误。这可能会在一个连接到管道的进程在另一个进程之前退出时发生。

0

我曾经遇到过类似的问题,即在一个线程中执行进程,该线程的运行方法将读取进程的InputStream,然后调用waitFor()方法。

我将进程InputStream的读取移动到了它自己的线程中,就像OP所做的那样,不再看到断开的管道退出代码返回。


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