Udførelse af shell-kommandoer i Java
On oktober 23, 2021 by adminIndledning
I denne artikel vil vi se på, hvordan vi kan udnytte Runtime
– og ProcessBuilder
-klasserne til at udføre shell-kommandoer og scripts med Java.
Vi bruger computere til at automatisere mange ting i vores daglige arbejde. Systemadministratorer kører hele tiden mange kommandoer, hvoraf nogle er meget gentagende og kræver minimale ændringer mellem kørslerne.
Denne proces er også moden til automatisering. Der er ingen grund til at køre alting manuelt. Ved hjælp af Java kan vi køre enkelte eller flere shell-kommandoer, udføre shell-scripts, køre terminalen/kommandoprompten, indstille arbejdskataloger og manipulere miljøvariabler via kerneklasser.
Runtime.exec()
Klassen Runtime
i Java er en klasse på højt niveau, der er til stede i hver eneste Java-applikation. Gennem den kommunikerer selve programmet med det miljø, det befinder sig i.
Gennem at udtrække den runtime, der er knyttet til vores program via getRuntime()
-metoden, kan vi bruge exec()
-metoden til at udføre kommandoer direkte eller køre .bat
/.sh
-filer.
exec()
-metoden tilbyder et par overloadede variationer:
-
public Process exec(String command)
– Udfører kommandoen indeholdt icommand
i en separat proces. -
public Process exec(String command, String envp)
– Udførercommand
, med et array af miljøvariabler. De leveres som et array af strenge, der følger formatetname=value
. -
public Process exec(String command, String envp, File dir)
– Udførercommand
, med de angivne miljøvariabler, fra mappendir
. -
public Process exec(String cmdArray)
– Udfører en kommando i form af et array af strenge. -
public Process exec(String cmdArray, String envp)
– Udfører en kommando med de angivne miljøvariabler. -
public Process exec(String cmdarray, String envp, File dir)
– Udfører en kommando med de angivne miljøvariabler inde fra mappendir
.
Det er værd at bemærke, at disse processer køres eksternt fra fortolkeren og vil være systemafhængige.
Det er også værd at bemærke forskellen mellem String command
og String cmdArray
. De opnår den samme ting. En command
er alligevel opdelt i et array, så brug af en af disse to burde give de samme resultater.
Det er op til dig at afgøre, om exec("dir /folder")
eller exec(new String{"dir", "/folder"}
er det, du vil bruge.
Lad os skrive et par eksempler for at se, hvordan disse overloadede metoder adskiller sig fra hinanden.
Udførelse af en kommando fra String
Lad os starte med den enkleste fremgangsmåde ud af disse tre:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Kørsel af denne kode vil udføre den kommando, som vi har leveret i String-format. Vi kan dog ikke se noget, når vi kører dette.
For at validere, om dette kørte korrekt, skal vi få fat i process
-objektet. Lad os bruge en BufferedReader
til at tage et kig på, hvad der foregår:
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); }}
Nu, når vi kører denne metode efter exec()
-metoden, bør det give noget i retning af:
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
Husk, at vi bliver nødt til at udtrække procesoplysningerne fra Process
-instanserne, når vi gennemgår andre eksempler.
Angiv arbejdskatalog
Hvis du gerne vil køre en kommando fra f.eks. en bestemt mappe, ville vi gøre noget i stil med:
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);
Her har vi forsynet exec()
-metoden med en command
, en null
til nye miljøvariabler og en new File()
, som er angivet som vores arbejdskatalog.
Den tilføjelse af cmd /c
før en kommando som dir
er værd at bemærke.
Da jeg arbejder på Windows, åbner dette cmd
, og /c
udfører den efterfølgende kommando. I dette tilfælde er det dir
.
Grunden til, at dette ikke var obligatorisk for ping
-eksemplet, men er obligatorisk for dette eksempel, er pænt besvaret af en SO-bruger.
Kørsel af det foregående stykke kode vil resultere i:
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
Lad os se på, hvordan vi kunne levere den foregående kommando i flere individuelle dele, i stedet for en enkelt String:
Process process = Runtime.getRuntime().exec( new String{"cmd", "/c", "dir"}, null, new File("C:\Users\")); printResults(process);
Kørsel af dette stykke kode vil også resultere i:
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
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
I sidste ende vil den indtastede kommando uanset fremgangsmåden – ved hjælp af en enkelt String eller et String-array – altid blive opdelt i et array, inden den bliver behandlet af den underliggende logik.
Hvilken du vil bruge koger blot ned til, hvilken du finder mere læsbar.
Anvendelse af miljøvariabler
Lad os se på, hvordan vi kan bruge miljøvariabler:
Process process = Runtime.getRuntime().exec( "cmd /c echo %var1%", new String{"var1=value1"}); printResults(process);
Vi kan angive så mange miljøvariabler, som vi vil, inden for String-arrayet. Her har vi lige udskrevet værdien af var1
ved hjælp af echo
.
Kørsel af denne kode vil give:
value1
Kørsel af .bat- og .sh-filer
I nogle tilfælde er det bare meget nemmere at aflaste alt til en fil og køre den fil i stedet for at tilføje alt programmatisk.
Afhængigt af dit styresystem bruger du enten .bat
eller .sh
-filer. Lad os oprette en med indholdet:
echo Hello World
Derpå bruger vi den samme fremgangsmåde som før:
Process process = Runtime.getRuntime().exec( "cmd /c start file.bat", null, new File("C:\Users\User\Desktop\"));
Dette vil åbne kommandoprompten og køre .bat
-filen i den arbejdskatalog, vi har indstillet.
Kørsel af denne kode resulterer sikkert nok i:
Med alle de overbelastede exec()
-signaturer taget hånd om, lad os tage et kig på ProcessBuilder
-klassen, og hvordan vi kan udføre kommandoer ved hjælp af den.
ProcessBuilder
ProcessBuilder
er den underliggende mekanisme, der kører kommandoerne, når vi bruger Runtime.getRuntime().exec()
-metoden:
/** * 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 for Runtime
-klassen
Tager vi et kig på, hvordan ProcessBuilder
tager vores input fra exec()
-metoden og kører kommandoen, får vi også et godt indblik i, hvordan vi kan bruge den.
Den accepterer en String cmdarray
, og det er nok til at få den til at køre. Alternativt kan vi forsyne den med valgfrie argumenter som String envp
og File dir
.
Lad os undersøge disse muligheder.
ProcessBuilder: I stedet for at kunne levere en enkelt streng, f.eks. cmd /c dir
, er vi i dette tilfælde nødt til at opdele den. Hvis vi f.eks. ville liste filerne i mappen C:/Users
som før, ville vi gøre:
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
For rent faktisk at udføre en Process
kører vi kommandoen start()
og tildeler den returnerede værdi til en Process
-instans.
Kørsel af denne kode vil give:
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
Denne fremgangsmåde er dog ikke bedre end den foregående. Det, der er nyttigt ved ProcessBuilder
-klassen, er, at den kan tilpasses. Vi kan indstille ting programmatisk, ikke kun via kommandoer.
ProcessBuilder: I stedet for at angive arbejdsmappen via kommandoen kan vi indstille den programmatisk:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
Her har vi indstillet arbejdsmappen til at være den samme som før, men vi har flyttet denne definition ud af selve kommandoen. Hvis du kører denne kode, får du det samme resultat som i det sidste eksempel.
ProcessBuilder: Environment Variables
Ved hjælp af ProcessBuilder
s metoder er det nemt at hente en liste over miljøvariabler i form af en Map
. Det er også nemt at indstille miljøvariabler, så dit program kan bruge dem.
Lad os hente de miljøvariabler, der er tilgængelige i øjeblikket, og derefter tilføje nogle til senere brug:
ProcessBuilder processBuilder = new ProcessBuilder();Map<String, String> environmentVariables = processBuilder.environment();environmentVariables.forEach((key, value) -> System.out.println(key + value));
Her har vi pakket de returnerede miljøvariabler ind i en Map
og kørt en forEach()
på den for at udskrive værdierne til vores konsol.
Kørsel af denne kode vil give en liste over de miljøvariabler, du har på din maskine:
DriverDataC:\Windows\System32\Drivers\DriverDataHerokuPathE:\HerokuProgramDataC:\ProgramData...
Nu skal vi tilføje en miljøvariabel til denne liste og bruge den:
environmentVariables.put("var1", "value1");processBuilder.command("cmd", "/c", "echo", "%var1%");Process process = processBuilder.start();printResults(process);
Kørsel af denne kode vil give:
value1
Når programmet er færdig med at køre, vil denne variabel naturligvis ikke forblive på listen.
ProcessBuilder: Hvis du vil køre en fil, skal vi igen bare forsyne ProcessBuilder
-instansen med de nødvendige oplysninger:
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
Kørsel af denne kode resulterer i, at kommandoprompten åbner og udfører .bat
-filen:
Konklusion
I denne artikel har vi udforsket eksempler på at køre shell-kommandoer i Java. Vi har brugt Runtime
– og ProcessBuilder
-klasserne til dette.
Med Java kan vi køre enkelte eller flere shell-kommandoer, udføre shell-scripts, køre terminalen/kommandoprompten, indstille arbejdskataloger og manipulere miljøvariabler via kerneklasser.
Skriv et svar