【问题标题】:Process output from apache-commons exec处理来自 apache-commons exec 的输出
【发布时间】:2011-11-12 12:25:13
【问题描述】:

到此为止,我已经束手无策了。我确信这很简单,而且我对 Java 和流的理解很可能存在巨大的漏洞。我认为有这么多类,我有点不知所措,试图通过 API 找出我想何时以及如何使用大量输入/输出流。

我刚刚了解到 apache commons 库的存在(自学 java 失败),目前正在尝试将我的一些 Runtime.getRuntime().exec 转换为使用 commons - exec。已经修复了一些每 6 个月出现一次的问题,然后解决 exec 的风格问题。

代码执行 perl 脚本,并在运行时在 GUI 中显示脚本的标准输出。

调用代码在 swingworker 内部。

我迷失了如何使用pumpStreamHandler...无论如何这里是旧代码:

String pl_cmd = "perl script.pl"
Process p_pl = Runtime.getRuntime().exec( pl_cmd );

BufferedReader br_pl = new BufferedReader( new InputStreamReader( p_pl.getInputStream() ) );

stdout = br_pl.readLine();
while ( stdout != null )
{
    output.displayln( stdout );
    stdout = br_pl.readLine();
}

我想这就是我很久以前不完全理解的复制粘贴代码所得到的。上面我假设正在执行该过程,然后获取输出流(通过“getInputStream”?),将其放入缓冲读取器中,然后将在那里循环直到缓冲区为空。

我不明白为什么这里不需要“等待”风格的命令?是否有可能在一段时间内缓冲区为空,退出循环并在进程仍在进行时继续?当我运行它时,情况似乎并非如此。

无论如何,我都在尝试使用 commons exec 获得相同的行为,基本上还是从谷歌找到的代码:

DefaultExecuteResultHandler rh = new DefaultExecuteResultHandler();
ExecuteWatchdog wd  = new ExecuteWatchdog( ExecuteWatchdog.INFINITE_TIMEOUT );
Executor exec = new DefaultExecutor();

ByteArrayOutputStream out = new ByteArrayOutputStream();
PumpStreamHandler psh = new PumpStreamHandler( out );

exec.setStreamHandler( psh );
exec.setWatchdog( wd );

exec.execute(cmd, rh );
rh.waitFor();

我试图弄清楚 pumpstreamhandler 在做什么。我假设这将从 exec 对象获取输出,并用 perl 脚本的 stdout/err 中的字节填充我提供的 OutputStream?

如果是这样,您将如何获得上述行为以使其逐行流式传输?在示例中,人们显示您最后调用了 out.toString(),我认为这只会在脚本运行完成后给我一个脚本的所有输出的转储?您将如何做到这一点,以便在逐行运行时显示输出?

------------未来编辑 ---------

通过谷歌找到这个并且效果很好:

public static void main(String a[]) throws Exception
{
    ByteArrayOutputStream stdout = new ByteArrayOutputStream();
    PumpStreamHandler psh = new PumpStreamHandler(stdout);
    CommandLine cl = CommandLine.parse("ls -al");
    DefaultExecutor exec = new DefaultExecutor();
    exec.setStreamHandler(psh);
    exec.execute(cl);
    System.out.println(stdout.toString());
}

【问题讨论】:

  • 我还试图从 ExecuteResultHandler 获取错误/输出/输入流并将其扔到 JTextPane 上。上面的方法有效还是您使用了 ExecuteResultHandler setOut/In/Error 来做到这一点?
  • 这不是你,而是 Java :-) 在(POSIX)脚本中使用“反引号”,甚至在 C(或其他 POSIX 友好语言)中使用“反引号”,甚至在 C(或其他 POSIX 友好的语言)中出现什么是微不足道的,都是一种复杂的愚蠢行为在 Java 中。

标签: java apache-commons apache-commons-exec


【解决方案1】:

我不明白为什么这里不需要“等待”风格的命令?是否有可能在一段时间内缓冲区为空,退出循环并在进程仍在进行时继续?当我运行它时,情况似乎并非如此。

readLine 块。也就是说,您的代码将等到一行被读取。

泵流处理程序

来自Documentation

将子进程的标准输出和错误复制到标准输出 和父进程的错误。如果输出或错误流设置为 null,来自该流的任何反馈都将丢失。

【讨论】:

  • 我看到并阅读了文档。除了打印到父母的标准输出之外,我只是不明白如何连接这些点以在另一个位置捕获输出。
  • 我相信您可以在 PumpStreamHandler 中传递不同的 OutputStream 而不是 ByteArrayOutputStream out = new ByteArrayOutputStream();(例如,您创建的文件输出流,以便将日志转储到文件中)。
  • 我猜是这样,在我跑到这里之前,我会尝试找到我需要传递的任何流,以便将字节流获取到字符流,然后进入缓冲读者,然后还想知道 exec 的行为是否不同。但我的猜测是代码基本上和前面的例子一样,只是必须将它包装在一个 pumpstreamhandler 中并将它传递给 exec,但保持一切相同。
【解决方案2】:

不要将ByteArrayOutputStream 传递给PumpStreamHandler,使用抽象类org.apache.commons.exec.LogOutputStream 的实现。来自the javadoc

实现解析传入的数据以构造一行并将完整的行传递给用户定义的实现。

因此,LogOutputSram 对输出进行预处理,以便您控制处理单个行而不是原始字节。像这样的:

import java.util.LinkedList;
import java.util.List;
import org.apache.commons.exec.LogOutputStream;

public class CollectingLogOutputStream extends LogOutputStream {
    private final List<String> lines = new LinkedList<String>();
    @Override protected void processLine(String line, int level) {
        lines.add(line);
    }   
    public List<String> getLines() {
        return lines;
    }
}

然后在阻塞调用exec.execute 之后,您的getLines() 将具有您正在寻找的标准输出和标准错误。 ExecutionResultHandler 从仅执行进程的角度来看是可选的,并将所有 stdOut/stdErr 收集到行列表中。

【讨论】:

  • 为什么不传递ByteArrayOutputStream?更多解释会有所帮助,而不是盲目使用LogOutputStream
【解决方案3】:

根据 James A Wilson 的回答,我创建了辅助类“Execute”。它包装了他的答案 为方便起见,该解决方案还提供了 exitValue。

以这种方式执行命令需要一行:

ExecResult result=Execute.execCmd(cmd,expectedExitCode);

以下 Junit Testcase 测试并展示了如何使用它:

Junit4 测试用例:

package com.bitplan.newsletter;

import static org.junit.Assert.*;

import java.util.List;

import org.junit.Test;

import com.bitplan.cmd.Execute;
import com.bitplan.cmd.Execute.ExecResult;

/**
 * test case for the execute class
 * @author wf
 *
 */
public class TestExecute {
     @Test
   public void testExecute() throws Exception {
     String cmd="/bin/ls";
     ExecResult result = Execute.execCmd(cmd,0);
     assertEquals(0,result.getExitCode());
     List<String> lines = result.getLines();
     assertTrue(lines.size()>0);
     for (String line:lines) {
         System.out.println(line);
     }
   }
}

执行 Java 助手类:

package com.bitplan.cmd;

import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;

import org.apache.commons.exec.CommandLine;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.LogOutputStream;
import org.apache.commons.exec.PumpStreamHandler;

/**
 * Execute helper using apache commons exed
 *
 *  add this dependency to your pom.xml:
   <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-exec</artifactId>
            <version>1.2</version>
        </dependency>

 * @author wf
 *
 */
public class Execute {

    protected static java.util.logging.Logger LOGGER = java.util.logging.Logger
            .getLogger("com.bitplan.cmd");

    protected final static boolean debug=true;

    /**
     * LogOutputStream
     * http://stackoverflow.com/questions/7340452/process-output-from
     * -apache-commons-exec
     * 
     * @author wf
     * 
     */
    public static class ExecResult extends LogOutputStream {
        private int exitCode;
        /**
         * @return the exitCode
         */
        public int getExitCode() {
            return exitCode;
        }

        /**
         * @param exitCode the exitCode to set
         */
        public void setExitCode(int exitCode) {
            this.exitCode = exitCode;
        }

        private final List<String> lines = new LinkedList<String>();

        @Override
        protected void processLine(String line, int level) {
            lines.add(line);
        }

        public List<String> getLines() {
            return lines;
        }
    }

    /**
     * execute the given command
     * @param cmd - the command 
     * @param exitValue - the expected exit Value
     * @return the output as lines and exit Code
     * @throws Exception
     */
    public static ExecResult execCmd(String cmd, int exitValue) throws Exception {
        if (debug)
            LOGGER.log(Level.INFO,"running "+cmd);
        CommandLine commandLine = CommandLine.parse(cmd);
        DefaultExecutor executor = new DefaultExecutor();
        executor.setExitValue(exitValue);
        ExecResult result =new ExecResult();
        executor.setStreamHandler(new PumpStreamHandler(result));
        result.setExitCode(executor.execute(commandLine));
        return result;
    }

}

【讨论】:

【解决方案4】:

这是一个非常古老的线程,但我不得不使用 Apache Commons Exec 并且必须解决同样的问题。我相信 2014 年发布的 Apache Commons Exec 的最新版本,以下解决方案在有和没有看门狗的情况下都能正常工作;

class CollectingLogOutputStream implements ExecuteStreamHandler {
private final List<String> lines = new LinkedList<String>();
public void setProcessInputStream(OutputStream outputStream) throws IOException 
{
}
//important - read all output line by line to track errors
public void setProcessErrorStream(InputStream inputStream) throws IOException {
    InputStreamReader isr = new InputStreamReader(inputStream);
    BufferedReader br = new BufferedReader(isr);
    String line="";
    while( (line = br.readLine()) != null){
        //use lines whereever you want - for now just print on console
        System.out.println("error:"+line);
    }
}
//important - read all output line by line to track process output
public void setProcessOutputStream(InputStream inputStream) throws IOException 
{
    InputStreamReader isr = new InputStreamReader(inputStream);
    BufferedReader br = new BufferedReader(isr);
    String line="";
    while( (line = br.readLine()) != null){
        //use lines whereever you want - for now just print on console
        System.out.println("output:"+line);
    }
  }

public void start() throws IOException {
}

public void stop() throws IOException {
}
}

上面的类可以设置为下面的执行器的StreamHandler;

//set newly created class stream handler for the executor
executor.setStreamHandler(new CollectingLogOutputStream());

完整的代码在这里; https://github.com/raohammad/externalprocessfromjava

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-10-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多