Esecuzione di comandi Shell in Java
Il Ottobre 23, 2021 da adminIntroduzione
In questo articolo, daremo un’occhiata a come possiamo sfruttare le classi Runtime
e ProcessBuilder
per eseguire comandi e script shell con Java.
Utilizziamo i computer per automatizzare molte cose nel nostro lavoro quotidiano. Gli amministratori di sistema eseguono molti comandi tutto il tempo, alcuni dei quali sono molto ripetitivi e richiedono cambiamenti minimi tra un’esecuzione e l’altra.
Anche questo processo è maturo per l’automazione. Non c’è bisogno di eseguire tutto manualmente. Usando Java, possiamo eseguire comandi di shell singoli o multipli, eseguire script di shell, eseguire il terminale / prompt dei comandi, impostare le directory di lavoro e manipolare le variabili di ambiente attraverso le classi del nucleo.
Runtime.exec()
La classe Runtime
in Java è una classe di alto livello, presente in ogni singola applicazione Java. Attraverso di essa, l’applicazione stessa comunica con l’ambiente in cui si trova.
Estraendo il runtime associato alla nostra applicazione tramite il metodo getRuntime()
, possiamo utilizzare il metodo exec()
per eseguire direttamente i comandi o eseguire i file .bat
/.sh
.
Il metodo exec()
offre alcune varianti sovraccaricate:
-
public Process exec(String command)
– Esegue il comando contenuto incommand
in un processo separato. -
public Process exec(String command, String envp)
– Esegue ilcommand
, con un array di variabili ambientali. Sono fornite come un array di stringhe, seguendo il formatoname=value
. -
public Process exec(String command, String envp, File dir)
– Esegue ilcommand
, con le variabili d’ambiente specificate, dall’interno della directorydir
. -
public Process exec(String cmdArray)
– Esegue un comando nella forma di un array di stringhe. -
public Process exec(String cmdArray, String envp)
– Esegue un comando con le variabili d’ambiente specificate. -
public Process exec(String cmdarray, String envp, File dir)
– Esegue un comando, con le variabili d’ambiente specificate, dall’interno della directorydir
.
E’ da notare che questi processi sono eseguiti esternamente all’interprete e dipendono dal sistema.
C’è anche da notare la differenza tra String command
e String cmdArray
. Essi ottengono la stessa cosa. Un command
è comunque suddiviso in un array, quindi usare uno qualsiasi di questi due dovrebbe dare gli stessi risultati.
Sta a voi decidere se exec("dir /folder")
o exec(new String{"dir", "/folder"}
è quello che vorreste usare.
Scriviamo qualche esempio per vedere come questi metodi sovraccaricati differiscono tra loro.
Eseguire un comando da una stringa
Partiamo con l’approccio più semplice di questi tre:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Eseguendo questo codice verrà eseguito il comando che abbiamo fornito in formato String. Tuttavia, non vediamo nulla quando lo eseguiamo.
Per validare se questo viene eseguito correttamente, vogliamo prendere in mano l’oggetto process
. Usiamo un BufferedReader
per dare un’occhiata a cosa sta succedendo:
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); }}
Ora, quando eseguiamo questo metodo dopo il metodo exec()
, dovrebbe produrre qualcosa di simile a:
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
Tenete a mente che dovremo estrarre le informazioni di processo dalle istanze Process
quando passeremo ad altri esempi.
Specificare la directory di lavoro
Se volete eseguire un comando da, diciamo, una certa cartella, dovremmo fare qualcosa del tipo:
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);
Qui, abbiamo fornito il metodo exec()
con un command
, un null
per le nuove variabili d’ambiente e un new File()
che è impostato come nostra directory di lavoro.
Degno di nota è l’aggiunta di cmd /c
prima di un comando come dir
.
Siccome sto lavorando su Windows, questo apre il cmd
e /c
esegue il comando successivo. In questo caso, è dir
.
Il motivo per cui questo non era obbligatorio per l’esempio ping
, ma è obbligatorio per questo esempio è stato ben risposto da un utente SO.
Eseguendo il pezzo di codice precedente si otterrà:
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
Diamo un’occhiata a come potremmo fornire il comando precedente in diverse parti individuali, invece di una singola stringa:
Process process = Runtime.getRuntime().exec( new String{"cmd", "/c", "dir"}, null, new File("C:\Users\")); printResults(process);
Eseguendo questo pezzo di codice si otterrà anche:
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
In definitiva, indipendentemente dall’approccio – usando una singola stringa o un array di stringhe, il comando inserito sarà sempre suddiviso in un array prima di essere elaborato dalla logica sottostante.
Qualunque sia l’approccio che vorresti usare, si riduce solo a quello che trovi più leggibile.
Usare le variabili d’ambiente
Diamo un’occhiata a come possiamo usare le variabili d’ambiente:
Process process = Runtime.getRuntime().exec( "cmd /c echo %var1%", new String{"var1=value1"}); printResults(process);
Possiamo fornire tutte le variabili d’ambiente che vogliamo nell’array di stringhe. Qui, abbiamo appena stampato il valore di var1
usando echo
.
Eseguendo questo codice otterremo:
value1
Esecuzione di file .bat e .sh
A volte, è molto più facile scaricare tutto in un file ed eseguire quel file invece di aggiungere tutto programmaticamente.
A seconda del tuo sistema operativo, dovresti usare file .bat
o .sh
. Creiamone uno con il contenuto:
echo Hello World
Poi, usiamo lo stesso approccio di prima:
Process process = Runtime.getRuntime().exec( "cmd /c start file.bat", null, new File("C:\Users\User\Desktop\"));
Questo aprirà il prompt dei comandi ed eseguirà il file .bat
nella directory di lavoro che abbiamo impostato.
Eseguendo questo codice si ottiene sicuramente:
Con tutte le firme exec()
sovraccaricate di cui ci siamo occupati, diamo un’occhiata alla classe ProcessBuilder
e a come possiamo eseguire comandi con essa.
ProcessBuilder
ProcessBuilder
è il meccanismo sottostante che esegue i comandi quando usiamo il metodo 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 per la classe Runtime
Dando uno sguardo a come il ProcessBuilder
prende il nostro input dal metodo exec()
ed esegue il comando, ci dà una buona idea di come usarlo.
Accetta un String cmdarray
, e questo è sufficiente per farlo funzionare. In alternativa, possiamo fornirgli argomenti opzionali come il String envp
e il File dir
.
Esaminiamo queste opzioni.
ProcessBuilder: Executing Command from Strings
Invece di poter fornire una singola stringa, come cmd /c dir
, dovremo spezzarla in questo caso. Per esempio, se volessimo elencare i file nella directory C:/Users
come prima, faremmo:
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
Per eseguire effettivamente un Process
, eseguiamo il comando start()
e assegniamo il valore restituito a un’istanza Process
.
Eseguendo questo codice si ottiene:
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
Tuttavia, questo approccio non è migliore di quello precedente. Ciò che è utile con la classe ProcessBuilder
è che è personalizzabile. Possiamo impostare le cose programmaticamente, non solo tramite comandi.
ProcessBuilder: Specify the Working Directory
Invece di fornire la directory di lavoro tramite il comando, impostiamola programmaticamente:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
Qui abbiamo impostato la directory di lavoro come prima, ma abbiamo spostato questa definizione fuori dal comando stesso. L’esecuzione di questo codice fornirà lo stesso risultato dell’ultimo esempio.
ProcessBuilder: Variabili d’ambiente
Utilizzando i metodi di ProcessBuilder
, è facile recuperare una lista di variabili d’ambiente sotto forma di Map
. È anche facile impostare le variabili d’ambiente in modo che il vostro programma possa usarle.
Prendiamo le variabili d’ambiente attualmente disponibili e poi aggiungiamone alcune per un uso successivo:
ProcessBuilder processBuilder = new ProcessBuilder();Map<String, String> environmentVariables = processBuilder.environment();environmentVariables.forEach((key, value) -> System.out.println(key + value));
Qui abbiamo impacchettato le variabili d’ambiente restituite in un Map
ed eseguito un forEach()
su di esso per stampare i valori sulla nostra console.
Eseguendo questo codice otterremo una lista delle variabili d’ambiente che avete sulla vostra macchina:
DriverDataC:\Windows\System32\Drivers\DriverDataHerokuPathE:\HerokuProgramDataC:\ProgramData...
Ora, aggiungiamo una variabile d’ambiente a quella lista e usiamola:
environmentVariables.put("var1", "value1");processBuilder.command("cmd", "/c", "echo", "%var1%");Process process = processBuilder.start();printResults(process);
Eseguendo questo codice otterremo:
value1
Naturalmente, una volta che il programma ha finito di funzionare, questa variabile non rimarrà nella lista.
ProcessBuilder: Esecuzione di file .bat e .sh
Se volete eseguire un file, di nuovo, dobbiamo solo fornire all’istanza ProcessBuilder
le informazioni richieste:
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
Eseguendo questo codice il prompt dei comandi si apre ed esegue il file .bat
:
Conclusione
In questo articolo, abbiamo esplorato esempi di esecuzione di comandi shell in Java. Abbiamo usato le classi Runtime
e ProcessBuilder
per farlo.
Utilizzando Java, possiamo eseguire comandi di shell singoli o multipli, eseguire script di shell, eseguire il terminale/prompt di comando, impostare le directory di lavoro e manipolare le variabili di ambiente attraverso le classi del nucleo.
Lascia un commento