Spouštění příkazů shellu v Javě
On 23 října, 2021 by adminÚvod
V tomto článku se podíváme, jak můžeme využít třídy Runtime
a ProcessBuilder
ke spouštění příkazů a skriptů shellu v Javě.
Počítače používáme k automatizaci mnoha věcí při každodenní práci. Správci systému neustále spouštějí mnoho příkazů, z nichž některé se velmi opakují a vyžadují minimální změny mezi jednotlivými spuštěními.
Tento proces je také zralý pro automatizaci. Není třeba vše spouštět ručně. Pomocí Javy můžeme spouštět jeden nebo více příkazů shellu, spouštět skripty shellu, spouštět terminál/příkazový řádek, nastavovat pracovní adresáře a manipulovat s proměnnými prostředí prostřednictvím základních tříd.
Runtime.exec()
Třída Runtime
v Javě je třída vysoké úrovně, přítomná v každé jednotlivé aplikaci Javy. Jejím prostřednictvím komunikuje samotná aplikace s prostředím, ve kterém se nachází.
Pomocí metody getRuntime()
získáme runtime spojený s naší aplikací a můžeme pomocí metody exec()
přímo spouštět příkazy nebo spouštět .bat
/.sh
soubory.
Metoda exec()
nabízí několik přetížených variant:
-
public Process exec(String command)
– Spustí příkaz obsažený vcommand
v samostatném procesu. -
public Process exec(String command, String envp)
– Spustícommand
s polem proměnných prostředí. Jsou poskytovány jako pole řetězců podle formátuname=value
. -
public Process exec(String command, String envp, File dir)
– Spustí příkazcommand
se zadanými proměnnými prostředí z adresáředir
. -
public Process exec(String cmdArray)
– Spustí příkaz ve formě pole řetězců. -
public Process exec(String cmdArray, String envp)
– Spustí příkaz se zadanými proměnnými prostředí. -
public Process exec(String cmdarray, String envp, File dir)
– Spustí příkaz se zadanými proměnnými prostředí z adresáředir
.
Je třeba poznamenat, že tyto procesy jsou spouštěny externě z interpretu a budou závislé na systému.
Za zmínku stojí také rozdíl mezi String command
a String cmdArray
. Dosahují totiž téhož. A command
je tak jako tak rozdělena na pole, takže použití kterékoli z těchto dvou metod by mělo přinést stejné výsledky.
Je na vás, abyste se rozhodli, zda chcete použít exec("dir /folder")
nebo exec(new String{"dir", "/folder"}
.
Napíšeme si několik příkladů, abychom viděli, jak se tyto přetížené metody od sebe liší.
Vykonání příkazu z řetězce
Začněme nejjednodušším přístupem z těchto tří:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Spuštěním tohoto kódu se provede příkaz, který jsme zadali ve formátu String. Po jeho spuštění však nic nevidíme.
Chceme-li ověřit, zda tento příkaz proběhl správně, budeme se chtít dostat k objektu process
. Použijme BufferedReader
, abychom se podívali, co se děje:
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); }}
Nyní, když spustíme tuto metodu po metodě exec()
, mělo by to dát něco ve smyslu:
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
Mějte na paměti, že budeme muset získat informace o procesu z instancí Process
, jak budeme procházet dalšími příklady.
Určení pracovního adresáře
Pokud bychom chtěli spustit příkaz například z určité složky, provedli bychom něco v tomto smyslu:
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);
Zde jsme metodu exec()
opatřili command
, null
pro nové proměnné prostředí a new File()
, která je nastavena jako náš pracovní adresář.
Za zmínku stojí přidání cmd /c
před příkaz, například dir
.
Protože pracuji ve Windows, otevře se cmd
a /c
provede následný příkaz. V tomto případě je to dir
.
Důvod, proč to nebylo povinné pro příklad ping
, ale je to povinné pro tento příklad, hezky zodpověděl uživatel SO.
Spuštění předchozího kusu kódu bude mít za následek:
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
Podívejme se, jak bychom mohli předchozí příkaz dodat v několika jednotlivých částech místo jednoho řetězce:
Process process = Runtime.getRuntime().exec( new String{"cmd", "/c", "dir"}, null, new File("C:\Users\")); printResults(process);
Spuštění tohoto kusu kódu bude mít také za následek:
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
Koneckonců, bez ohledu na přístup – použití jednoho řetězce nebo pole řetězců, bude zadaný příkaz před zpracováním základní logikou vždy rozdělen na pole.
Který z těchto způsobů chcete použít, se omezuje pouze na to, který vám připadá čitelnější.
Použití proměnných prostředí
Podívejme se, jak můžeme použít proměnné prostředí:
Process process = Runtime.getRuntime().exec( "cmd /c echo %var1%", new String{"var1=value1"}); printResults(process);
V rámci pole String můžeme zadat libovolný počet proměnných prostředí. Zde jsme právě vypsali hodnotu var1
pomocí echo
.
Spuštění tohoto kódu vrátí:
value1
Spuštění souborů .bat a .sh
Někdy je prostě mnohem jednodušší vše přenést do souboru a spustit tento soubor, než vše přidávat programově.
V závislosti na operačním systému použijete buď soubory .bat
nebo .sh
. Vytvořme jeden s obsahem:
echo Hello World
Poté použijeme stejný postup jako dříve:
Process process = Runtime.getRuntime().exec( "cmd /c start file.bat", null, new File("C:\Users\User\Desktop\"));
Tím se otevře příkazový řádek a spustí se soubor .bat
v pracovním adresáři, který jsme nastavili.
Spouštění tohoto kódu jistě dostatečně vyústí v:
Když máme o všechny přetížené signatury exec()
postaráno, podívejme se na třídu ProcessBuilder
a na to, jak pomocí ní můžeme spouštět příkazy.
ProcessBuilder
ProcessBuilder
je základní mechanismus, který spouští příkazy, když použijeme metodu 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 pro třídu Runtime
Podíváme-li se na to, jak ProcessBuilder
přijme náš vstup z metody exec()
a spustí příkaz, získáme dobrou představu o tom, jak ji také použít.
Přijímá příkaz String cmdarray
, a to stačí k jeho spuštění. Případně mu můžeme dodat nepovinné argumenty, jako jsou String envp
a File dir
.
Prozkoumejme tyto možnosti.
ProcessBuilder: Spouštění příkazů z řetězců
Místo toho, abychom mohli zadat jediný řetězec, například cmd /c dir
, budeme jej muset v tomto případě rozdělit. Pokud bychom například chtěli vypsat soubory v adresáři C:/Users
jako dříve, provedli bychom:
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
Pro skutečné vykonání Process
spustíme příkaz start()
a vrácenou hodnotu přiřadíme instanci Process
.
Provedením tohoto kódu získáme:
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
Tento přístup však není o nic lepší než předchozí. Na třídě ProcessBuilder
je užitečné to, že ji lze přizpůsobit. Můžeme nastavovat věci programově, nejen pomocí příkazů.
ProcessBuilder: Místo zadávání pracovního adresáře prostřednictvím příkazu jej nastavíme programově:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
Zde jsme nastavili pracovní adresář stejně jako dříve, ale tuto definici jsme přesunuli mimo samotný příkaz. Spuštění tohoto kódu poskytne stejný výsledek jako minulý příklad.
ProcessBuilder: Proměnné prostředí
Pomocí metod ProcessBuilder
s lze snadno získat seznam proměnných prostředí ve formě Map
. Je také snadné nastavit proměnné prostředí tak, aby je váš program mohl používat.
Získejme aktuálně dostupné proměnné prostředí a pak přidejme některé pro pozdější použití:
ProcessBuilder processBuilder = new ProcessBuilder();Map<String, String> environmentVariables = processBuilder.environment();environmentVariables.forEach((key, value) -> System.out.println(key + value));
Vrácené proměnné prostředí jsme zabalili do Map
a spustili na ně forEach()
, abychom hodnoty vypsali na naši konzolu.
Spuštěním tohoto kódu získáme seznam proměnných prostředí, které máte na svém počítači:
DriverDataC:\Windows\System32\Drivers\DriverDataHerokuPathE:\HerokuProgramDataC:\ProgramData...
Nyní do tohoto seznamu přidáme proměnnou prostředí a použijeme ji:
environmentVariables.put("var1", "value1");processBuilder.command("cmd", "/c", "echo", "%var1%");Process process = processBuilder.start();printResults(process);
Spuštěním tohoto kódu získáme:
value1
Po dokončení běhu programu samozřejmě tato proměnná v seznamu nezůstane.
ProcessBuilder: Spouštění souborů .bat a .sh
Pokud byste chtěli spustit soubor, opět bychom jen dodali instanci ProcessBuilder
požadované informace:
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
Spuštění tohoto kódu vede k otevření příkazového řádku a spuštění souboru .bat
:
Závěr
V tomto článku jsme se zabývali příklady spouštění příkazů shellu v Javě. Použili jsme k tomu třídy Runtime
a ProcessBuilder
.
Pomocí jazyka Java můžeme spouštět jeden nebo více příkazů shellu, spouštět skripty shellu, spouštět terminál/příkazový řádek, nastavovat pracovní adresáře a manipulovat s proměnnými prostředí prostřednictvím základních tříd.
Napsat komentář