Overview
W tym tutorialu zilustrujemy dwa sposoby wykonania polecenia powłoki z kodu Java.
Pierwszym jest użycie klasy Runtime i wywołanie jej metody exec.
Drugim i bardziej konfigurowalnym sposobem, będzie stworzenie i użycie instancji ProcessBuilder.
Zależność od systemu operacyjnego
Zanim utworzymy nowy proces wykonujący nasze polecenie powłoki, musimy najpierw określić system operacyjny, na którym działa nasza maszyna JVM.
To dlatego, że w systemie Windows musimy uruchomić nasze polecenie jako argument powłoki cmd.exe, a na wszystkich innych systemach operacyjnych możemy wydać standardową powłokę, zwaną sh:
boolean isWindows = System.getProperty("os.name") .toLowerCase().startsWith("windows");
Wejście i wyjście
Ponadto potrzebujemy sposobu, aby podłączyć się do strumieni wejścia i wyjścia naszego procesu.
Przynajmniej wyjście musi zostać skonsumowane – w przeciwnym razie nasz proces nie wróci pomyślnie, zamiast tego zawiesi się.
Zaimplementujmy powszechnie używaną klasę o nazwie StreamGobbler, która konsumuje strumień wejściowy:
private static class StreamGobbler implements Runnable { private InputStream inputStream; private Consumer<String> consumer; public StreamGobbler(InputStream inputStream, Consumer<String> consumer) { this.inputStream = inputStream; this.consumer = consumer; } @Override public void run() { new BufferedReader(new InputStreamReader(inputStream)).lines() .forEach(consumer); }}
UWAGA: Ta klasa implementuje interfejs Runnable, co oznacza, że może być wykonana przez dowolnego Executora.
Runtime.exec()
Wywołanie metody Runtime.exec() jest prostym, jeszcze nie konfigurowalnym, sposobem na wywołanie nowego podprocesu.
W poniższym przykładzie zażądamy listy katalogów użytkownika i wydrukujemy ją na konsoli:
String homeDirectory = System.getProperty("user.home");Process process;if (isWindows) { process = Runtime.getRuntime() .exec(String.format("cmd.exe /c dir %s", homeDirectory));} else { process = Runtime.getRuntime() .exec(String.format("sh -c ls %s", homeDirectory));}StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);Executors.newSingleThreadExecutor().submit(streamGobbler);int exitCode = process.waitFor();assert exitCode == 0;
ProcessBuilder
Do drugiej implementacji naszego problemu obliczeniowego użyjemy ProcessBuildera. Jest to preferowane w stosunku do podejścia Runtime, ponieważ jesteśmy w stanie dostosować niektóre szczegóły.
Na przykład jesteśmy w stanie:
- zmienić katalog roboczy, w którym działa nasze polecenie powłoki, używając builder.directory()
- ustawić niestandardową mapę klucz-wartość jako środowisko używając builder.environment()
- przekierować strumienie wejściowe i wyjściowe do niestandardowych zamienników
- dziedziczyć oba z nich do strumieni bieżącego procesu JVM używając builder.inheritIO()
ProcessBuilder builder = new ProcessBuilder();if (isWindows) { builder.command("cmd.exe", "/c", "dir");} else { builder.command("sh", "-c", "ls");}builder.directory(new File(System.getProperty("user.home")));Process process = builder.start();StreamGobbler streamGobbler = new StreamGobbler(process.getInputStream(), System.out::println);Executors.newSingleThreadExecutor().submit(streamGobbler);int exitCode = process.waitFor();assert exitCode == 0;
Zakończenie
Jak widzieliśmy w tym krótkim tutorialu, możemy wykonać polecenie powłoki w Javie na dwa różne sposoby.
Ogólnie, jeśli planujesz dostosować wykonanie spawnu procesu, na przykład, aby zmienić jego katalog roboczy, powinieneś rozważyć użycie ProcessBuilder.