Shell-parancsok végrehajtása Javában
On október 23, 2021 by adminBevezetés
Ebben a cikkben megnézzük, hogyan használhatjuk a Runtime és ProcessBuilder osztályokat shell-parancsok és szkriptek futtatására Javával.
A számítógépeket sok minden automatizálására használjuk a napi munkánk során. A rendszergazdák állandóan sok parancsot futtatnak, amelyek közül néhány nagyon ismétlődő, és minimális változtatásokat igényel a futtatások között.
Ez a folyamat is megérett az automatizálásra. Nincs szükség arra, hogy mindent kézzel futtassunk. A Java segítségével egyetlen vagy több shell parancsot futtathatunk, shell szkripteket futtathatunk, a terminál/parancssorozatot futtathatjuk, munkakönyvtárakat állíthatunk be és környezeti változókat manipulálhatunk a core osztályokon keresztül.
Runtime.exec()
A Runtime osztály a Java-ban egy magas szintű osztály, amely minden egyes Java alkalmazásban jelen van. Ezen keresztül maga az alkalmazás kommunikál azzal a környezettel, amelyben van.
Az alkalmazásunkhoz tartozó futási időt a getRuntime() metóduson keresztül kivonva, a exec() metódus segítségével közvetlenül tudunk parancsokat végrehajtani vagy .bat/.sh fájlokat futtatni.
A exec() módszer néhány túlterhelt változatot kínál:
-
public Process exec(String command)– Acommand-ben szereplő parancsot hajtja végre egy külön folyamatban. -
public Process exec(String command, String envp)– Végrehajtja acommand-t, a környezeti változók tömbjével. Ezeket Strings tömbként adja meg, aname=valueformátumot követve. -
public Process exec(String command, String envp, File dir)– Végrehajtja acommandparancsot, a megadott környezeti változókkal, adirkönyvtárból. -
public Process exec(String cmdArray)– Végrehajtja a parancsot egy Strings tömb formájában. -
public Process exec(String cmdArray, String envp)– Végrehajtja a parancsot a megadott környezeti változókkal. -
public Process exec(String cmdarray, String envp, File dir)– Parancsot hajt végre a megadott környezeti változókkal adirkönyvtárból.
Érdemes megjegyezni, hogy ezek a folyamatok az értelmezőn kívül futnak, és rendszerfüggőek lesznek.
Azt is érdemes megjegyezni, hogy a String command és a String cmdArray között van különbség. Ugyanazt a dolgot érik el. Egy command amúgy is tömbre bomlik, így e kettő bármelyikének használata ugyanazt az eredményt fogja eredményezni.
Az már rajtad múlik, hogy a exec("dir /folder") vagy a exec(new String{"dir", "/folder"} az, amit használni szeretnél.
Írjunk fel néhány példát, hogy lássuk, miben különböznek ezek a túlterhelt módszerek egymástól.
Parancs végrehajtása stringből
Kezdjük a legegyszerűbb megközelítéssel a három közül:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
A kód futtatásával végrehajtjuk az általunk megadott parancsot string formátumban. Azonban nem látunk semmit, amikor ezt futtatjuk.
Az érvényesítéshez, hogy ez helyesen futott-e, a process objektumot kell megszereznünk. Használjunk egy BufferedReader-et, hogy megnézzük, mi történik:
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); }}
Most, amikor ezt a metódust a exec() metódus után futtatjuk, valami olyasmit kell eredményeznie, mint:
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
Ne feledjük, hogy a folyamatinformációkat a Process példányokból kell majd kinyernünk, ahogy más példákon keresztül haladunk.
Munkakönyvtár megadása
Ha egy parancsot mondjuk egy bizonyos mappából szeretnénk futtatni, akkor valami olyasmit teszünk, mint:
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);
Itt a exec() metódust egy command, egy null az új környezeti változóknak és egy new File(), amit a munkakönyvtárunknak állítunk be.
Az cmd /c hozzáadása egy olyan parancs előtt, mint a dir, érdemes megjegyezni.
Mivel Windowson dolgozom, ez megnyitja a cmd és a /c végrehajtja a következő parancsot. Ebben az esetben ez a dir.
Az okot, hogy ez miért nem volt kötelező a ping példánál, de ennél a példánál kötelező, egy SO felhasználó szépen megválaszolta.
Az előző kódrészlet futtatása a következőt eredményezi:
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
Nézzük meg, hogy az előző parancsot egyetlen karakterlánc helyett több különálló részben is megadhatjuk:
Process process = Runtime.getRuntime().exec( new String{"cmd", "/c", "dir"}, null, new File("C:\Users\")); printResults(process);
A kódrészlet futtatása szintén a következőt eredményezi:
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
Végeredményben a megközelítéstől függetlenül – egyetlen String vagy egy String tömb használata – a beírt parancsot mindig tömbre bontjuk, mielőtt a mögöttes logika feldolgozza.
Az, hogy melyiket szeretnénk használni, csak azon múlik, hogy melyiket találjuk olvashatóbbnak.
Környezeti változók használata
Nézzük meg, hogyan használhatjuk a környezeti változókat:
Process process = Runtime.getRuntime().exec( "cmd /c echo %var1%", new String{"var1=value1"}); printResults(process);
A String tömbön belül annyi környezeti változót adhatunk meg, amennyit csak szeretnénk. Itt éppen a var1 értékét nyomtattuk ki a echo használatával.
A kód futtatásával:
value1
A .bat és .sh fájlok futtatása
Néha sokkal egyszerűbb mindent egy fájlba pakolni és azt a fájlt futtatni ahelyett, hogy mindent programozottan adnánk hozzá.
Az operációs rendszertől függően a .bat vagy a .sh fájlokat használjuk. Hozzunk létre egyet a következő tartalommal:
echo Hello World
Ezután használjuk ugyanazt a megközelítést, mint korábban:
Process process = Runtime.getRuntime().exec( "cmd /c start file.bat", null, new File("C:\Users\User\Desktop\"));
Ez megnyitja a parancssort, és futtatja a .bat fájlt az általunk beállított munkakönyvtárban.
Ez a kód futtatása elég biztosan azt eredményezi:
Mivel az összes túlterhelt exec() aláírásról gondoskodtunk, nézzük meg a ProcessBuilder osztályt és azt, hogyan tudunk parancsokat végrehajtani a segítségével.
ProcessBuilder
ProcessBuilder az a mögöttes mechanizmus, amely lefuttatja a parancsokat, amikor a Runtime.getRuntime().exec() metódust használjuk:
/** * 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 a Runtime osztályhoz
Megnézzük, hogyan veszi a ProcessBuilder a exec() metódusból származó bemenetünket és futtatja a parancsot, és ezzel egy jó ötletet kapunk arra, hogyan használjuk azt is.
Elvesz egy String cmdarray-at, és ez elég ahhoz, hogy futtassuk. Alternatívaként olyan opcionális argumentumokkal is elláthatjuk, mint a String envp és a File dir.
Vizsgáljuk meg ezeket a lehetőségeket.
ProcessBuilder: Parancs végrehajtása karakterláncokból
Ahelyett, hogy egyetlen karakterláncot tudnánk megadni, mint például a cmd /c dir, ebben az esetben fel kell bontanunk. Ha például a korábbiakhoz hasonlóan a C:/Users könyvtárban lévő fájlokat szeretnénk felsorolni, akkor:
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
Az Process tényleges végrehajtásához a start() parancsot futtatjuk, és a visszaadott értéket egy Process példányhoz rendeljük.
A kód futtatásával:
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
Ez a megközelítés azonban semmivel sem jobb, mint az előző. Ami hasznos a ProcessBuilder osztályban, az az, hogy testre szabható. Programozottan is beállíthatunk dolgokat, nem csak parancsokon keresztül.
ProcessBuilder: A munkakönyvtár megadása
Ahelyett, hogy a parancson keresztül adnánk meg a munkakönyvtárat, állítsuk be programozottan:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
Itt a munkakönyvtárat ugyanarra állítjuk be, mint korábban, de ezt a meghatározást kivettük magából a parancsból. Ezt a kódot futtatva ugyanazt az eredményt kapjuk, mint az előző példában.
ProcessBuilder: Környezeti változók
A ProcessBuilder módszereit használva könnyen lekérdezhetjük a környezeti változók listáját egy Map formájában. A környezeti változókat is könnyen beállíthatjuk, hogy a programunk használni tudja őket.
Kérjük le a jelenleg rendelkezésre álló környezeti változókat, majd adjunk hozzá néhányat későbbi használatra:
ProcessBuilder processBuilder = new ProcessBuilder();Map<String, String> environmentVariables = processBuilder.environment();environmentVariables.forEach((key, value) -> System.out.println(key + value));
Itt a visszakapott környezeti változókat egy Map-be csomagoltuk, és egy forEach()-t futtattunk rajta, hogy kiírjuk az értékeket a konzolunkra.
Ezt a kódot futtatva megkapjuk a gépünkön lévő környezeti változók listáját:
DriverDataC:\Windows\System32\Drivers\DriverDataHerokuPathE:\HerokuProgramDataC:\ProgramData...
Most, adjunk hozzá egy környezeti változót ehhez a listához, és használjuk fel:
environmentVariables.put("var1", "value1");processBuilder.command("cmd", "/c", "echo", "%var1%");Process process = processBuilder.start();printResults(process);
Ezt a kódot futtatva megkapjuk:
value1
A program futásának befejezése után természetesen ez a változó nem marad a listában.
ProcessBuilder: .bat és .sh fájlok futtatása
Ha egy fájlt szeretnénk futtatni, ismét csak a ProcessBuilder példányt látjuk el a szükséges információkkal:
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
A kód futtatásának eredményeképpen megnyílik a parancssor és végrehajtja a .bat fájlt:
Következtetés
Ebben a cikkben a héjparancsok Java nyelven történő futtatásának példáit vizsgáltuk. Ehhez a Runtime és ProcessBuilder osztályokat használtuk.
A Java segítségével egyetlen vagy több shell parancsot futtathatunk, shell szkripteket futtathatunk, a terminál/parancssorozatot futtathatjuk, munkakönyvtárakat állíthatunk be és környezeti változókat manipulálhatunk a core osztályok segítségével.
Vélemény, hozzászólás?