Java — постоянно используйте Input и OutputStream ProcessBuilder

Я хочу использовать внешний инструмент при извлечении некоторых данных (цикл через строки). Для этого я сначала использовал Runtime.getRuntime().exec() для его выполнения. Но потом моя добыча стала очень медленной. Поэтому я ищу возможность запускать внешний инструмент в каждом экземпляре цикла, используя один и тот же экземпляр оболочки.

Я узнал, что я должен использовать ProcessBuilder. Но это еще не работает.

Вот мой код для проверки выполнения (с учетом ответов здесь, на форуме уже):

public class ExecuteShell {
   ProcessBuilder builder;
   Process process = null;
   BufferedWriter process_stdin;
   BufferedReader reader, errReader;

   public ExecuteShell() {
    String command;
    command = getShellCommandForOperatingSystem();
    if(command.equals("")) {
        return; //Fehler!  No error handling yet
    }
    //init shell
    builder = new ProcessBuilder( command);
    builder.redirectErrorStream(true);
    try {
        process = builder.start();
    } catch (IOException e) {
        System.out.println(e);
    }

    //get stdout of shell  
    reader    = new BufferedReader(new InputStreamReader(process.getInputStream()));  
    errReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));

    //get stdin of shell
    process_stdin = new BufferedWriter(new OutputStreamWriter(process.getOutputStream()));
    System.out.println("ExecuteShell: Constructor successfully finished");
   }

   public String executeCommand(String commands) {
    StringBuffer output;
    String line;
    try {
        //single execution
        process_stdin.write(commands);
        process_stdin.newLine();
        process_stdin.flush();
    } catch (IOException e) {
        System.out.println(e);
    }
    output    = new StringBuffer();
    line = ""; 

    try {
        if (!reader.ready()) {
            output.append("Reader empty \n");
            return output.toString();
        }
        while ((line = reader.readLine())!= null) {
            output.append(line + "\n");
            return output.toString();
        }
        if (!reader.ready()) {
            output.append("errReader empty \n");
            return output.toString();
        }
        while ((line = errReader.readLine())!= null) {
            output.append(line + "\n");
        }
    } catch (Exception e) {
        System.out.println("ExecuteShell: error in executeShell2File");
        e.printStackTrace();
        return "";
    }
    return output.toString();
   }


   public int close() {
    // finally close the shell by execution exit command
    try {
        process_stdin.write("exit");
        process_stdin.newLine();
        process_stdin.flush();
    }
    catch (IOException e) {
        System.out.println(e);
        return 1;
    }
    return 0;
   }

   private static String getShellCommandForOperatingSystem() {
    Properties prop = System.getProperties( );
    String os =  prop.getProperty( "os.name" );
    if ( os.startsWith("Windows") ) {
        //System.out.println("WINDOWS!");
        return "C:/cygwin64/bin/bash";
    } else if (os.startsWith("Linux") ) { 
        //System.out.println("Linux!");
        return"/bin/sh";
    }
    return "";      
   }
}

Я хочу назвать это в другом классе, таком как этот Testclass:

public class TestExec{
    public static void main(String[] args) {
        String result = "";
        ExecuteShell es = new ExecuteShell();
        for (int i=0; i<5; i++) {
          // do something
          result = es.executeCommand("date"); //execute some command
          System.out.println("result:\n" + result); //do something with result
          // do something
        }
        es.close();
    }
}

Моя проблема в том, что выходной поток всегда пуст:

ExecuteShell: Constructor successfully finished
result:
Reader empty 

result:
Reader empty 

result:
Reader empty 

result:
Reader empty 

result:
Reader empty 

Я прочитал ветку здесь: Java Process with Input/Output Stream

Но фрагментов кода было недостаточно, чтобы заставить меня двигаться, я что-то упускаю. Я действительно не очень много работал с разными потоками. И я не уверен, поможет ли мне сканер. Я был бы очень признателен за помощь.

В конечном счете, моя цель состоит в том, чтобы многократно вызывать внешнюю команду и делать это быстро.

РЕДАКТИРОВАТЬ: я изменил цикл, так что es.close() находится снаружи. И я хотел добавить, что я не хочу только этого внутри цикла.

РЕДАКТИРОВАТЬ: Проблема со временем заключалась в том, что команда, которую я вызвал, вызвала ошибку. Когда команда не вызывает ошибки, время приемлемо.

Спасибо за ответ


person emi-le    schedule 12.01.2015    source источник


Ответы (1)


Вероятно, вы столкнулись с состоянием гонки: после записи команды в оболочку ваша Java-программа продолжает работать и почти сразу же вызывает reader.ready(). Команда, которую вы хотели выполнить, вероятно, еще ничего не вывела, поэтому у читателя нет доступных данных. Альтернативным объяснением может быть то, что команда ничего не записывает в stdout, а только в stderr (или оболочку, может быть, она не смогла запустить команду?). Однако на практике вы не читаете из stderr.

Чтобы правильно обрабатывать потоки вывода и ошибок, вы не можете проверять reader.ready(), но должны вызывать readLine() (который ожидает, пока данные не будут доступны) в цикле. С вашим кодом, даже если бы программа дошла до этой точки, вы прочитали бы только ровно одну строку из вывода. Если бы программа вывела более одной строки, эти данные были бы интерпретированы как вывод следующей команды. Типичное решение состоит в том, чтобы читать в цикле до тех пор, пока readLine() не вернет null, но здесь это не работает, потому что это означало бы, что ваша программа будет ждать в этом цикле, пока оболочка не завершит работу (чего никогда не произойдет, поэтому она будет просто зависать бесконечно). Исправить это практически невозможно, если вы точно не знаете, сколько строк каждая команда запишет в stdout и stderr.

Однако ваш сложный подход с использованием оболочки и отправкой команд в нее, вероятно, совершенно не нужен. Запуск команды из вашей Java-программы и из оболочки выполняется одинаково быстро и намного проще в написании. Точно так же нет разницы в производительности между Runtime.exec() и ProcessBuilder (первый просто вызывает второй), вам нужен только ProcessBuilder, если вам нужны его расширенные функции.

Если вы испытываете проблемы с производительностью при вызове внешних программ, вы должны выяснить, где именно они находятся, и попытаться решить их, но не таким подходом. Например, обычно запускается поток для чтения как из вывода, так и из потока ошибок (если не запускать отдельные потоки и команда выдает большой вывод, все может зависнуть). Это может быть медленным, поэтому вы можете использовать пул потоков, чтобы избежать повторного создания процессов.

person Philipp Wendler    schedule 12.01.2015
comment
Чтобы добавить: обычно лучше не использовать Java в качестве замены bash. - person Ian McLaird; 12.01.2015
comment
Здравствуйте, Филипп, спасибо за ваш ответ. Когда я не проверил, готов ли читатель. Ответа не было вообще (я даже ждал несколько минут.....). Вот почему я добавил оператор return в цикле while. Потому что я сначала подумал, что, может быть, это зависание там (я просто забыл об этом, когда выкладывал сюда свой код). Я просто не получаю вывод оболочки. Я думаю, что команда действительно не началась. Что вы имеете в виду под запуском команды из вашей Java-программы и из оболочки одинаково быстро и намного проще в написании? Вы имеете в виду скрипт bash вместо java-программы? - person emi-le; 13.01.2015
comment
Я имею в виду, что вам вообще не следует заморачиваться с оболочкой, а вместо этого запускать команду, которую вы хотите выполнить (в вашем случае это date), напрямую с помощью Runtime.exec() или ProcessBuilder. - person Philipp Wendler; 13.01.2015
comment
Я хочу вызвать cs2cs, и поэтому мне нужно включить трубу или ‹ в мою команду. Вот как я начал использовать оболочку (было перед Рождеством, поэтому я забыл). Я не мог заставить работать потоки ввода и вывода, пытаясь сделать что-то вроде объяснения здесь: stackoverflow.com/questions/11336157/ (та же проблема, что и выше. Программа зависает, не получая вывода) - person emi-le; 13.01.2015
comment
Если вам нужна труба (|), вам действительно нужна оболочка. Тем не менее, я предлагаю каждый раз запускать новую оболочку с чем-то вроде /bin/sh -c 'command | command'. Накладные расходы на запуск оболочки в дополнение к командам должны быть незначительными, и это значительно облегчит вам задачу. - person Philipp Wendler; 13.01.2015