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 InputStream
czytać tylko z jednego . Wcześniej trzeba było mieć dwa oddzielne wątki, jeden odczyt z stdout
i 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/bash
z 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 test
a następnie exit
program wyjdzie z pierwszej pętli od momentu zakończenia cmd.exe
procesu. 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ę,
exit
a następnie echo test
otrzymuję 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, writer
następującą pętlą:
while (scan.hasNext()) {
String input = scan.nextLine();
if (input.trim().equals("exit")) {
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--
.
bash
zgł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.