Proces Java ze strumieniem wejścia/Wyjścia

Poniżej przedstawiam poniższy przykład kodu. Dzięki czemu można wprowadzić polecenie do powłoki bash tj. echo test i mieć wynik echo ' D z powrotem. Jednak po pierwszym czytaniu. Inne strumienie wyjściowe nie działają?

Dlaczego robię coś złego? Moim celem końcowym jest stworzenie wątkowego zaplanowanego zadania, które okresowo wykonuje polecenie / bash, aby OutputStream i InputStream musiały działać w tandemie i nie przestawały działać. Również doświadczam błędu java.io.IOException: Broken pipe KAŻDEGO pomysły?

Dzięki.
String line;
Scanner scan = new Scanner(System.in);

Process process = Runtime.getRuntime ().exec ("/bin/bash");
OutputStream stdin = process.getOutputStream ();
InputStream stderr = process.getErrorStream ();
InputStream stdout = process.getInputStream ();

BufferedReader reader = new BufferedReader (new InputStreamReader(stdout));
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin));

String input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.flush();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}

input = scan.nextLine();
input += "\n";
writer.write(input);
writer.close();

while ((line = reader.readLine ()) != null) {
System.out.println ("Stdout: " + line);
}
 82
Author: Yves M., 2010-09-05

3 answers

Po pierwsze, polecam zastąpienie linii

Process process = Runtime.getRuntime ().exec ("/bin/bash");

Z wierszami

ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();

ProcessBuilder jest nowością w Javie 5 i ułatwia uruchamianie zewnętrznych procesów. Moim zdaniem jego najbardziej znaczącym ulepszeniem w stosunku do Runtime.getRuntime().exec() jest to, że pozwala przekierować standardowy błąd procesu potomnego na jego standardowe wyjście. Oznacza to, że masz tylko jeden InputStream do przeczytania. Wcześniej trzeba było mieć dwa oddzielne wątki, jeden odczyt z stdout i jeden odczyt z stderr, aby uniknąć napełnienia standardowego bufora błędu, gdy standardowy bufor wyjściowy był pusty (powodując zawieszenie procesu potomnego) lub odwrotnie.

Następnie pętle (z których masz dwie)

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

Kończy działanie tylko wtedy, gdy reader, który odczytuje ze standardowego wyjścia procesu, zwraca koniec pliku. Dzieje się tak tylko wtedy, gdy proces {[9] } zakończy działanie. Nie zwróci końca pliku, jeśli w chwili obecnej nie ma już wyjścia z procesu. Zamiast tego będzie czekać na następny linia wyjścia z procesu i nie powróci, dopóki nie będzie miała tej następnej linii.

Ponieważ wysyłasz do procesu dwie linie wejściowe przed osiągnięciem tej pętli, pierwsza z tych dwóch pętli zawiesi się, jeśli proces nie zakończył się po tych dwóch liniach wejściowych. Będzie tam czekał na kolejną linijkę do przeczytania, ale nigdy nie będzie kolejnej linijki do przeczytania.

Skompilowałem Twój kod źródłowy (w tej chwili jestem na Windowsie, więc zamieniłem /bin/bash na cmd.exe, ale zasady powinny być takie same) i stwierdziłem, że:

  • Po wpisaniu w dwóch wierszach pojawia się wyjście z dwóch pierwszych poleceń, ale potem program zawiesza się,
  • jeśli wpiszę echo test, a następnie exit, Program wydostanie się z pierwszej pętli po zakończeniu procesu cmd.exe. Następnie program pyta o inną linię danych wejściowych( która jest ignorowana), przeskakuje bezpośrednio przez drugą pętlę, ponieważ proces potomny już się zakończył, a następnie sam się kończy.
  • jeśli Wpisuję exit, a potem echo test, dostaję IOException narzekający na zamknięcie rury. Należy się tego spodziewać-pierwsza linia wejścia spowodowała zakończenie procesu i nie ma gdzie wysłać drugiej linii.

Widziałem sztuczkę, która robi coś podobnego do tego, co wydaje się chcieć, w programie, nad którym pracowałem. Program ten przechowywał wiele powłok, uruchamiał w nich polecenia i odczytywał dane wyjściowe z tych poleceń. Sztuczka polegała na tym, aby zawsze wypisywać 'magiczna' linia oznaczająca koniec wyjścia polecenia powłoki i używająca go do określenia, kiedy wyjście z polecenia wysłanego do powłoki zostało zakończone.

Wziąłem Twój kod i zamieniłem wszystko po linii, która przypisuje się writer na następującą pętlę:

while (scan.hasNext()) {
    String input = scan.nextLine();
    if (input.trim().equals("exit")) {
        // Putting 'exit' amongst the echo --EOF--s below doesn't work.
        writer.write("exit\n");
    } else {
        writer.write("((" + input + ") && echo --EOF--) || echo --EOF--\n");
    }
    writer.flush();

    line = reader.readLine();
    while (line != null && ! line.trim().equals("--EOF--")) {
        System.out.println ("Stdout: " + line);
        line = reader.readLine();
    }
    if (line == null) {
        break;
    }
}

Po zrobieniu tego, mogłem niezawodnie uruchomić kilka komend i Kazdy z nich wróci do mnie indywidualnie.

Dwa echo --EOF-- polecenia w linii wysyłanej do powłoki są tam, aby zapewnić, że wyjście polecenia jest zakończone znakiem --EOF-- nawet w wyniku błędu z polecenia.

Oczywiście takie podejście ma swoje ograniczenia. Ograniczenia te obejmują:

  • jeśli wprowadzę polecenie, które czeka na wejście użytkownika( np. inną powłokę), program wydaje się zawieszać,
  • zakłada, że każdy proces uruchamiany przez powłokę kończy swój wynik znakiem nowej linii,
  • robi się trochę zdezorientowany, jeśli polecenie uruchamiane przez powłokę zdarzy się wypisać linię --EOF--.
  • bash zgłasza błąd składni i kończy działanie, jeśli wpiszesz jakiś tekst z niedopasowanym ).

Te punkty mogą nie mieć dla ciebie znaczenia, jeśli cokolwiek myślisz o uruchomieniu zaplanowanego zadania będzie ograniczone do polecenia lub małego zestawu komend, które nigdy nie będą zachowywać się w tak patologiczny sposób.

EDIT : popraw obsługę wyjścia i inne drobne zmiany po uruchomieniu tego na Linuksie.

 125
Author: Luke Woodward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-23 21:17:30

Myślę, że możesz użyć wątku takiego jak demon-thread do odczytu danych wejściowych, a Twój czytnik wyjściowy będzie już w pętli while W głównym wątku, więc możesz czytać i pisać w tym samym czasie.Możesz zmodyfikować swój program w następujący sposób:

Thread T=new Thread(new Runnable() {

    @Override
    public void run() {
        while(true)
        {
            String input = scan.nextLine();
            input += "\n";
            try {
                writer.write(input);
                writer.flush();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

        }

    }
} );
T.start();

I możesz być taki sam jak wyżej tzn.

while ((line = reader.readLine ()) != null) {
    System.out.println ("Stdout: " + line);
}

Spraw, aby twój pisarz był ostateczny, w przeciwnym razie nie będzie mógł być dostępny przez wewnętrzną klasę.

 4
Author: Shashank Jain,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-12 13:45:39

Masz writer.close(); w swoim kodzie. Tak więc bash otrzymuje EOF na swoim stdin i wychodzi. Następnie otrzymujesz Broken pipe podczas próby odczytania z stdoutnieistniejącego Basha.

 1
Author: gpeche,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-09-04 22:01:16