Proces Java ze strumieniem wejścia / wyjścia


90

Mam następujący przykład kodu poniżej. Dzięki czemu możesz wprowadzić polecenie do powłoki bash tj. echo testI otrzymać wynik z powrotem. Jednak po pierwszym czytaniu. Inne strumienie wyjściowe nie działają?

Dlaczego tak się dzieje lub robię coś źle? Moim celem końcowym jest utworzenie zaplanowanego zadania Threaded, które okresowo wykonuje polecenie do / bash, aby OutputStreami InputStreammusiał pracować w tandemie i nie przerywać pracy. Mam też błąd java.io.IOException: Broken pipejakieś 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);
}

„Uszkodzony potok” prawdopodobnie oznacza, że ​​proces potomny został zakończony. Nie przejrzałem w pełni reszty kodu, aby zobaczyć, jakie są inne problemy.
vanza

1
użyj oddzielnych wątków, będzie działać dobrze
Johnydep,

Odpowiedzi:


139

Po pierwsze, poleciłbym wymianę linki

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

z liniami

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

ProcessBuilder jest nowością w Javie 5 i ułatwia uruchamianie procesów zewnętrznych. Moim zdaniem najbardziej znaczącą poprawą w stosunku do niego Runtime.getRuntime().exec()jest to, że pozwala przekierować standardowy błąd procesu potomnego na jego standardowe wyjście. Oznacza to, że możesz InputStreamczytać tylko z jednego . Wcześniej trzeba było mieć dwa oddzielne wątki, jeden odczyt z stdouti jeden odczyt z stderr, aby uniknąć wypełniania bufora błędów standardowego, gdy standardowy bufor wyjściowy był pusty (co powodowało 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 reader, gdy , który czyta ze standardowego wyjścia procesu, zwraca koniec pliku. Dzieje się tak tylko wtedy, gdybash proces kończy się. Nie zwróci końca pliku, jeśli obecnie nie ma już wyjścia z procesu. Zamiast tego będzie czekał na następną linię danych wyjściowych procesu i nie wróci, dopóki nie otrzyma następnej linii.

Ponieważ wysyłasz dwa wiersze danych wejściowych do procesu 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 wierszach danych wejściowych. Będzie tam czekał na przeczytanie kolejnego wiersza, ale nigdy nie będzie kolejnego wiersza do przeczytania.

I skompilowany kod źródłowy (jestem na Windows w tej chwili, więc wymieniłem /bin/bashz cmd.exe, ale zasady powinny być takie same), i stwierdził, że:

  • po wpisaniu dwóch wierszy pojawia się wynik pierwszych dwóch poleceń, ale potem program się zawiesza,
  • jeśli napiszę, powiedzmy,, echo testa następnie exitprogram wyjdzie z pierwszej pętli od momentu zakończenia cmd.exeprocesu. Następnie program pyta o kolejny wiersz danych wejściowych (który jest ignorowany), przeskakuje bezpośrednio przez drugą pętlę, ponieważ proces potomny już zakończył działanie, a następnie kończy się.
  • jeśli wpisuję, exita następnie echo testotrzymuję IOException narzekający na zamknięcie potoku. Należy się tego spodziewać - pierwsza linia danych wejściowych spowodowała zakończenie procesu i nie ma dokąd wysłać drugiej linii.

Widziałem sztuczkę, która robi coś podobnego do tego, czego chcesz, w programie, nad którym pracowałem. Ten program utrzymywał wiele powłok, wykonywał w nich polecenia i odczytywał dane wyjściowe tych poleceń. Użyta sztuczka polegała na tym, aby zawsze wypisywać „magiczną” linię, która oznacza koniec wyjścia polecenia powłoki i używać jej do określenia, kiedy dane wyjściowe polecenia wysłanego do powłoki zakończyły się.

Wziąłem twój kod i zastąpiłem wszystko po linii, która przypisuje, writernastę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 wykonaniu tej czynności mogłem niezawodnie uruchomić kilka poleceń i uzyskać dane wyjściowe z każdego z nich indywidualnie.

Dwa echo --EOF--polecenia w linii wysyłanej do powłoki mają zapewnić, że wyjście polecenia zostanie zakończone --EOF--nawet w wyniku błędu polecenia.

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

  • jeśli wpiszę polecenie, które czeka na wejście użytkownika (np. inna powłoka), program wygląda na zawieszony,
  • zakłada, że ​​każdy proces uruchamiany przez powłokę kończy swoje wyjście znakiem nowej linii,
  • robi się trochę zdezorientowany, jeśli polecenie uruchamiane przez powłokę wypisze linię --EOF--.
  • bashzgłasza błąd składni i kończy działanie, jeśli wprowadzisz tekst z niedopasowanym tekstem ).

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

EDYCJA : popraw obsługę wyjścia i inne drobne zmiany po uruchomieniu tego w systemie Linux.


Dziękuję za wyczerpującą odpowiedź Jednak myślę, że zidentyfikowałem prawdziwy przypadek moich problemów. Zwrócono mi uwagę w Twoim poście. stackoverflow.com/questions/3645889/… . Dziękuję Ci.
James Moore,

1
zamiana / bin / bash na cmd nie zachowywała się tak samo, ponieważ stworzyłem program, który robił to samo, ale miał problemy z bash. W mycase otwieranie trzech oddzielnych wątków dla każdego wejścia / wyjścia / błędu działa najlepiej bez problemu w przypadku poleceń interaktywnych z długimi sesjami.
Johnydep,


@AlexMills: czy Twój drugi komentarz oznacza, że ​​rozwiązałeś problem? W pierwszym komentarzu nie ma wystarczająco dużo szczegółów, aby powiedzieć, na czym polega problem, ani dlaczego „nagle” pojawiają się wyjątki.
Luke Woodward

@Luke, link, który podałem tuż nad twoim komentarzem, rozwiązuje mój problem
Alexander Mills,

4

Myślę, że możesz użyć wątku takiego jak demon-thread do czytania twoich danych wejściowych, a twój czytnik wyjściowy będzie już w pętli while w głównym wątku, dzięki czemu 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 czytelnik będzie taki sam jak powyżej tj

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

uczyń swojego pisarza ostatecznym, w przeciwnym razie nie będzie dostępny dla klasy wewnętrznej.


1

Masz writer.close();w swoim kodzie. Więc bash otrzymuje EOF na swoim stdini wychodzi. Wtedy dostajesz się, Broken pipegdy próbujesz czytać z stdoutnieistniejącego basha.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.