Подтвердить что ты не робот

Вывод процесса из apache-commons exec

Я нахожусь здесь на своем пути. Я уверен, что это что-то простое, и у меня, скорее всего, есть огромные дыры в моем понимании java и потоков. Я думаю, что существует так много классов, что я немного перегружен попытками проникнуть через API, чтобы выяснить, когда и как я хочу использовать множество потоков ввода/вывода.

Я только что узнал о существовании библиотеки apache commons (самообучение java fail), и в настоящее время я пытаюсь преобразовать часть моего Runtime.getRuntime(). exec для использования commons - exec. Уже исправлено несколько раз, каждые 6 месяцев эта проблема возникает, а затем устраняет проблемы стиля с exec.

Код выполняет perl script и отображает stdout из script в графическом интерфейсе, когда он запущен.

Вызывающий код находится внутри 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"?), Помещает его в буферизованный читатель, затем будет просто зацикливаться до тех пор, пока буфер не будет пуст.

Я не понимаю, почему здесь нет необходимости в команде стиля waitfor? Разве не возможно, что будет какое-то время, в течение которого буфер будет пустым, выйти из цикла и продолжить, пока процесс все еще продолжается? Когда я запускаю его, это, похоже, не так.

В любом случае, я пытаюсь получить такое же поведение, используя commons exec, в основном снова перейдя от найденного Google кода:

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

Я пытаюсь выяснить, что делает handstreamhandler. Я предполагаю, что это приведет к выходу из объекта exec и заполнит OutputStream. Я предоставляю ему байты из perl script stdout/err?

Если да, то как бы вы получили вышеприведенное поведение, чтобы поток был потоковым потоком? В примерах люди показывают, что вы вызываете out.toString() в конце, и я предполагаю, что это просто даст мне дамп всего вывода из script после его выполнения? Как бы вы сделали это так, чтобы он показывал результат, поскольку он работает по строкам?

------------ Future Edit ---------------------

Обнаружено это через google и хорошо работает:

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());
}
4b9b3361

Ответ 1

Не передавайте ByteArrayOutputStream в PumpStreamHandler, используйте реализацию абстрактного класса org.apache.commons.exec.LogOutputStream. От Javadoc:

Реализация анализирует входящие данные для построения строки и передает всю строку в пользовательскую реализацию.

Таким образом, LogOutputStram предварительно обрабатывает вывод, чтобы дать вам возможность управлять отдельными строками вместо необработанных байтов. Что-то вроде этого:

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() будет иметь стандартную ошибку out и стандартную ошибку, которую вы ищете. ExecutionResultHandler является необязательным с точки зрения простого выполнения процесса и сбора всего stdOut/stdErr в список строк.

Ответ 2

Я не понимаю, почему здесь нет необходимости в команде стиля waitfor? Разве не возможно, что будет какое-то время, в течение которого буфер будет пустым, выйти из цикла и продолжить, пока процесс все еще продолжается? Когда я запускаю его, это, похоже, не так.

readLine. То есть ваш код будет ждать, пока строка не будет прочитана.

PumpStreamHandler

из Документация

Копирует стандартный вывод и ошибку подпроцессов на стандартный вывод и ошибка родительского процесса. Если выходной поток или поток ошибок установлены на null, любая обратная связь от этого потока будет потеряна.

Ответ 3

Основываясь на ответе Джеймса Уилсона, я создал вспомогательный класс "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 и решить ту же проблему. Я доверяю последней версии Apache Commons Exec, опубликованной в 2014 году, нижеприведенное решение хорошо работает как со сторожевым таймером, так и без него;

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 {
    //InputStream is = ...; // keyboard, file or Internet
    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