Java - Използвайте непрекъснато Input и OutputStream на ProcessBuilder

Искам да използвам външен инструмент, докато извличам някои данни (преминаване през линии). За това първо използвах Runtime.getRuntime().exec(), за да го изпълня. Но тогава извличането ми стана много бавно. Така че търся възможност за изпълнение на външния инструмент във всеки екземпляр на цикъла, използвайки същия екземпляр на shell.

Разбрах, че трябва да използвам 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 процес с входно/изходен поток

Но кодовите фрагменти не бяха достатъчни, за да продължа, нещо ми липсва. Всъщност не съм работил много с различни теми. И не съм сигурен дали/как скенерът ми е полезен. Наистина ще се радвам на малко помощ.

В крайна сметка целта ми е да извикам външна команда многократно и да я направя бързо.

РЕДАКТИРАНЕ: Промених цикъла, така че 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