Wykonywanie poleceń powłoki w Javie
On 23 października, 2021 by adminWprowadzenie
W tym artykule przyjrzymy się, jak możemy wykorzystać klasy Runtime
i ProcessBuilder
do wykonywania poleceń powłoki i skryptów w Javie.
Korzystamy z komputerów, aby zautomatyzować wiele rzeczy w naszej codziennej pracy. Administratorzy systemów wykonują wiele poleceń przez cały czas, niektóre z nich są bardzo powtarzalne i wymagają minimalnych zmian pomiędzy kolejnymi uruchomieniami.
Ten proces jest również dojrzały do automatyzacji. Nie ma potrzeby, aby uruchamiać wszystko ręcznie. Używając Javy, możemy uruchamiać pojedyncze lub wielokrotne polecenia powłoki, wykonywać skrypty powłoki, uruchamiać terminal/polecenie prompt, ustawiać katalogi robocze i manipulować zmiennymi środowiskowymi poprzez klasy podstawowe.
Runtime.exec()
Klasa Runtime
w Javie jest klasą wysokiego poziomu, obecną w każdej pojedynczej aplikacji Javy. Za jej pośrednictwem sama aplikacja komunikuje się ze środowiskiem, w którym się znajduje.
Wyodrębniając runtime związany z naszą aplikacją za pomocą metody getRuntime()
, możemy użyć metody exec()
do bezpośredniego wykonywania poleceń lub uruchamiania plików .bat
/.sh
.
Metoda exec()
oferuje kilka przeciążonych odmian:
-
public Process exec(String command)
– Wykonuje polecenie zawarte wcommand
w oddzielnym procesie. -
public Process exec(String command, String envp)
– Wykonujecommand
, z tablicą zmiennych środowiskowych. Są one podawane jako tablica ciągów znaków, zgodnie z formatemname=value
. -
public Process exec(String command, String envp, File dir)
– Wykonuje poleceniecommand
, z podanymi zmiennymi środowiskowymi, z wnętrza katalogudir
. -
public Process exec(String cmdArray)
– Wykonuje polecenie w postaci tablicy ciągów znaków. -
public Process exec(String cmdArray, String envp)
– Wykonuje polecenie z podanymi zmiennymi środowiskowymi. -
public Process exec(String cmdarray, String envp, File dir)
– Wykonuje polecenie, z podanymi zmiennymi środowiskowymi, z wnętrza katalogudir
.
Warto zauważyć, że te procesy są uruchamiane na zewnątrz interpretera i będą zależne od systemu.
Warto również zauważyć różnicę między String command
a String cmdArray
. Osiągają one to samo. A command
i tak jest rozbity na tablicę, więc użycie któregokolwiek z tych dwóch powinno dać takie same rezultaty.
To ty decydujesz, czy exec("dir /folder")
czy exec(new String{"dir", "/folder"}
jest tym, czego chcesz użyć.
Zapiszmy kilka przykładów, aby zobaczyć, jak te przeciążone metody różnią się od siebie.
Wykonanie polecenia z String
Zacznijmy od najprostszego podejścia z tych trzech:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Wykonanie tego kodu spowoduje wykonanie polecenia, które dostarczyliśmy w formacie String. Jednak nic nie widzimy, gdy to uruchamiamy.
Aby sprawdzić, czy to zadziałało poprawnie, będziemy chcieli uzyskać dostęp do obiektu process
. Użyjmy BufferedReader
, aby spojrzeć, co się dzieje:
public static void printResults(Process process) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { System.out.println(line); }}
Teraz, gdy uruchomimy tę metodę po metodzie exec()
, powinna ona dać coś w rodzaju:
Pinging www.stackabuse.com with 32 bytes of data:Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Ping statistics for 104.18.57.23: Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),Approximate round trip times in milli-seconds: Minimum = 21ms, Maximum = 21ms, Average = 21ms
Pamiętajmy, że będziemy musieli wydobyć informacje o procesie z instancji Process
, gdy będziemy przechodzić przez inne przykłady.
Określ katalog roboczy
Jeśli chciałbyś uruchomić polecenie z, powiedzmy, określonego folderu, zrobilibyśmy coś w stylu:
Process process = Runtime.getRuntime() .exec("cmd /c dir", null, new File("C:\Users\")); //.exec("sh -c ls", null, new File("Pathname")); for non-Windows usersprintResults(process);
Tutaj, wyposażyliśmy metodę exec()
w command
, null
dla nowych zmiennych środowiskowych i new File()
, który jest ustawiony jako nasz katalog roboczy.
Na uwagę zasługuje dodanie cmd /c
przed poleceniem takim jak dir
.
Ponieważ pracuję na Windowsie, otwiera to cmd
i /c
wykonuje kolejne polecenie. W tym przypadku jest to dir
.
Powód, dla którego nie było to obowiązkowe dla przykładu ping
, ale jest obowiązkowe dla tego przykładu, jest ładnie odpowiedziane przez użytkownika SO.
Running the previous piece of code will result in:
Volume in drive C has no label. Volume Serial Number is XXXX-XXXX Directory of C:\Users08/29/2019 05:01 PM <DIR> .08/29/2019 05:01 PM <DIR> ..08/18/2016 09:11 PM <DIR> Default.migrated08/29/2019 05:01 PM <DIR> Public05/15/2020 11:08 AM <DIR> User 0 File(s) 0 bytes 5 Dir(s) 212,555,214,848 bytes free
Zobaczmy, jak moglibyśmy dostarczyć poprzednie polecenie w kilku pojedynczych częściach, zamiast pojedynczego String:
Process process = Runtime.getRuntime().exec( new String{"cmd", "/c", "dir"}, null, new File("C:\Users\")); printResults(process);
Running this piece of code will also result in:
Volume in drive C has no label. Volume Serial Number is XXXX-XXXX Directory of C:\Users08/29/2019 05:01 PM <DIR> .08/29/2019 05:01 PM <DIR> ..08/18/2016 09:11 PM <DIR> Default.migrated08/29/2019 05:01 PM <DIR> Public05/15/2020 11:08 AM <DIR> User 0 File(s) 0 bytes 5 Dir(s) 212,542,808,064 bytes free
Ostatecznie, niezależnie od podejścia – używając pojedynczego String lub tablicy String, wprowadzona przez Ciebie komenda zawsze zostanie rozbita na tablicę zanim zostanie przetworzona przez bazową logikę.
Którego z nich chcesz użyć sprowadza się tylko do tego, które z nich uważasz za bardziej czytelne.
Używanie zmiennych środowiskowych
Przyjrzyjrzyjmy się jak możemy używać zmiennych środowiskowych:
Process process = Runtime.getRuntime().exec( "cmd /c echo %var1%", new String{"var1=value1"}); printResults(process);
Możemy dostarczyć tyle zmiennych środowiskowych ile chcemy wewnątrz tablicy String. Tutaj właśnie wypisaliśmy wartość var1
, używając echo
.
Wykonanie tego kodu zwróci:
value1
Uruchamianie plików .bat i .sh
Czasami o wiele łatwiej jest po prostu przeładować wszystko do pliku i uruchomić ten plik, zamiast dodawać wszystko programowo.
Zależnie od systemu operacyjnego, użyjemy plików .bat
lub .sh
. Utwórzmy jeden z nich o następującej zawartości:
echo Hello World
Następnie zastosujmy to samo podejście, co poprzednio:
Process process = Runtime.getRuntime().exec( "cmd /c start file.bat", null, new File("C:\Users\User\Desktop\"));
To otworzy wiersz poleceń i uruchomi plik .bat
w katalogu roboczym, który ustawiliśmy.
Wykonanie tego kodu z pewnością zaowocuje:
Po zajęciu się wszystkimi przeciążonymi sygnaturami exec()
przyjrzyjmy się klasie ProcessBuilder
i temu, jak możemy wykonywać polecenia za jej pomocą.
ProcessBuilder
ProcessBuilder
jest podstawowym mechanizmem, który uruchamia komendy, gdy używamy metody Runtime.getRuntime().exec()
:
/** * Executes the specified command and arguments in a separate process with * the specified environment and working directory. *...*/public Process exec(String cmdarray, String envp, File dir) throws IOException { return new ProcessBuilder(cmdarray) .environment(envp) .directory(dir) .start();}
JavaDocs dla klasy Runtime
Patrząc na to, jak ProcessBuilder
pobiera dane wejściowe z metody exec()
i uruchamia komendę, mamy dobry pomysł, jak z niej korzystać.
Przyjmuje ona String cmdarray
, i to wystarczy, by ją uruchomić. Alternatywnie, możemy dostarczyć mu opcjonalne argumenty, takie jak String envp
i File dir
.
Poznajmy te opcje.
ProcessBuilder: Executing Command from Strings
Zamiast możliwości podania pojedynczego Stringa, takiego jak cmd /c dir
, w tym przypadku będziemy musieli go rozbić. Na przykład, gdybyśmy chcieli wylistować pliki w katalogu C:/Users
tak jak poprzednio, zrobilibyśmy:
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
Aby faktycznie wykonać Process
, uruchamiamy polecenie start()
i przypisujemy zwróconą wartość do instancji Process
.
Wykonanie tego kodu da nam:
Volume in drive C has no label. Volume Serial Number is XXXX-XXXX Directory of C:\Users08/29/2019 05:01 PM <DIR> .08/29/2019 05:01 PM <DIR> ..08/18/2016 09:11 PM <DIR> Default.migrated08/29/2019 05:01 PM <DIR> Public05/15/2020 11:08 AM <DIR> User 0 File(s) 0 bytes 5 Dir(s) 212,517,294,080 bytes free
Jednakże to podejście nie jest wcale lepsze od poprzedniego. To co jest przydatne w klasie ProcessBuilder
to fakt, że jest ona konfigurowalna. Możemy ustawiać rzeczy programowo, a nie tylko za pomocą komend.
ProcessBuilder: Specify the Working Directory
Zamiast podawać katalog roboczy za pomocą polecenia, ustawmy go programowo:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
Tutaj ustawiliśmy katalog roboczy na taki sam jak poprzednio, ale przenieśliśmy tę definicję poza samo polecenie. Uruchomienie tego kodu da taki sam rezultat jak w ostatnim przykładzie.
ProcessBuilder: Environment Variables
Używając metod ProcessBuilder
s, łatwo jest pobrać listę zmiennych środowiskowych w postaci Map
. Łatwo jest również ustawić zmienne środowiskowe tak, aby Twój program mógł z nich korzystać.
Pobierzmy obecnie dostępne zmienne środowiskowe, a następnie dodajmy kilka do późniejszego wykorzystania:
ProcessBuilder processBuilder = new ProcessBuilder();Map<String, String> environmentVariables = processBuilder.environment();environmentVariables.forEach((key, value) -> System.out.println(key + value));
Tutaj spakowaliśmy zwrócone zmienne środowiskowe do Map
i uruchomiliśmy na niej forEach()
, aby wypisać wartości na naszą konsolę.
W wyniku uruchomienia tego kodu otrzymamy listę zmiennych środowiskowych, które mamy na swoim komputerze:
DriverDataC:\Windows\System32\Drivers\DriverDataHerokuPathE:\HerokuProgramDataC:\ProgramData...
Dodajmy teraz zmienną środowiskową do tej listy i użyjmy jej:
environmentVariables.put("var1", "value1");processBuilder.command("cmd", "/c", "echo", "%var1%");Process process = processBuilder.start();printResults(process);
W wyniku uruchomienia tego kodu otrzymamy:
value1
Oczywiście, po zakończeniu działania programu zmienna ta nie pozostanie na liście.
ProcessBuilder: Running .bat and .sh Files
Jeśli chcielibyśmy uruchomić plik, ponownie, wystarczy, że dostarczymy instancji ProcessBuilder
wymagane informacje:
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
Wykonanie tego kodu skutkuje otwarciem wiersza poleceń i wykonaniem pliku .bat
:
Podsumowanie
W tym artykule zbadaliśmy przykłady uruchamiania poleceń powłoki w Javie. Wykorzystaliśmy do tego klasy Runtime
i ProcessBuilder
.
Używając Javy, możemy uruchamiać pojedyncze lub wielokrotne polecenia powłoki, wykonywać skrypty powłoki, uruchamiać terminal/polecenie zachęty, ustawiać katalogi robocze i manipulować zmiennymi środowiskowymi za pomocą klas podstawowych.
.
Dodaj komentarz