CodeGym /Corsi /JAVA 25 SELF /ProcessBuilder — avvio di processi esterni

ProcessBuilder — avvio di processi esterni

JAVA 25 SELF
Livello 61 , Lezione 0
Disponibile

1. Che cosa sono i processi nel sistema operativo e perché avviarli da Java

Processi: dalla JVM a bash e ritorno

Quando accendete il computer e aprite un browser, un messenger o un gioco — sono tutti processi separati. Il sistema operativo avvia per ogni programma un proprio “mini-mondo”: assegna memoria, tempo CPU, consente di lavorare con file e rete. Così i programmi convivono senza intralciarsi a vicenda.

Un programma Java non fa eccezione. Quando avviate java MyApp, il sistema crea per esso un processo separato con tutte le risorse necessarie. Al suo interno gira il vostro programma: calcola, disegna, legge file — tutto ciò che deve fare.

Ma a volte Java da sola non basta. Può capitare di dover chiedere aiuto a un altro programma — avviare un archiver per impacchettare file, chiamare ffmpeg per elaborare un video, o semplicemente scoprire quale versione di Java è installata sul computer. Questo è l’avvio di un processo esterno: Java dice al sistema “esegui questa utility”, e poi riceve il risultato del suo lavoro.

In sostanza, è un modo per rendere il programma più flessibile: combinare strumenti diversi, automatizzare lavori di routine o integrare la vostra logica in processi di sistema già esistenti. A volte è più semplice chiedere a un comando esterno di fare una parte del lavoro che reinventare la ruota nel codice.

JVM vs. processo esterno

Processo JVM — il vostro programma che gira nella Java Virtual Machine.

Processo esterno — qualsiasi altro programma: calcolatrice, script Python, riga di comando, persino un’altra istanza di Java.

2. Classe ProcessBuilder

Nei “tempi antichi” Java avviava i processi tramite il metodo Runtime.getRuntime().exec(). Non era il modo più comodo e sicuro — come provare a piantare un chiodo con un microscopio. Perciò da Java 5 è arrivata la classe ProcessBuilder, che permette di creare, configurare e avviare processi esterni in modo più flessibile e chiaro.

ProcessBuilder è una sorta di “costruttore” che consente di impostare in anticipo tutti i parametri del futuro processo: comando, argomenti, cartella di lavoro, variabili d’ambiente, ecc.

Sintassi: creazione di un processo

ProcessBuilder pb = new ProcessBuilder("comando", "argomento1", "argomento2", ...);
  • Il primo argomento è il nome del comando (ad esempio, "ls", "dir", "ping", "java").
  • Gli altri sono i parametri del comando.

Esempio: eseguire il comando ls (Linux/Mac) oppure dir (Windows)

ProcessBuilder pb;
if (System.getProperty("os.name").toLowerCase().contains("win")) {
    pb = new ProcessBuilder("cmd.exe", "/c", "dir");
} else {
    pb = new ProcessBuilder("ls", "-l");
}

A proposito, su Windows comandi come dir, copy ecc. non sono eseguibili separati, ma comandi “integrati” nella riga di comando (cmd.exe). Per questo devono essere avviati tramite cmd.exe /c ....

Esempio: avviare un processo semplice

ProcessBuilder pb = new ProcessBuilder("echo", "Hello, Java!");

3. Configurazione dell'ambiente del processo

Passaggio degli argomenti. Gli argomenti del comando si passano come stringhe separate:

ProcessBuilder pb = new ProcessBuilder("ping", "google.com");

Impostazione della directory di lavoro. Per impostazione predefinita il processo si avvia nella stessa cartella del vostro programma. Ma è possibile indicare esplicitamente un’altra directory:

pb.directory(new java.io.File("/tmp"));      // Per Linux/Mac
pb.directory(new java.io.File("C:\\Temp"));  // Per Windows

Modifica delle variabili d'ambiente. Ogni processo ha il proprio set di variabili d’ambiente (environment variables). È possibile aggiungerle o modificarle:

pb.environment().put("MY_VAR", "HelloFromJava");

Può essere utile se il processo esterno si aspetta variabili specifiche.

4. Avvio del processo

Metodo start(). Quando avete configurato tutto, è ora di avviare il processo:

Process process = pb.start();

Il metodo start() restituisce un oggetto Process, che permette di gestire il programma avviato: leggerne l’output, scriverne l’input, terminarlo, ecc.

Gestione delle eccezioni. start() può generare IOException se il comando non viene trovato, non ci sono i permessi o si verifica un altro errore di avvio.

Esempio:

try {
    Process process = pb.start();
    // Lavoriamo con il processo...
} catch (IOException e) {
    System.out.println("Errore di avvio del processo: " + e.getMessage());
}

5. Pratica: avvio di comandi semplici

Esempio 1: visualizzare l'elenco dei file in una cartella

import java.io.*;

public class ProcessDemo {
    public static void main(String[] args) {
        // Determiniamo il comando in base al sistema operativo
        ProcessBuilder pb;
        if (System.getProperty("os.name").toLowerCase().contains("win")) {
            pb = new ProcessBuilder("cmd.exe", "/c", "dir");
        } else {
            pb = new ProcessBuilder("ls", "-l");
        }

        try {
            Process process = pb.start();

            // Leggiamo l'output del processo (stdout)
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(process.getInputStream())
            );
            String line;
            while ((line = reader.readLine()) != null) {
                System.out.println(line);
            }

            // Attendiamo la terminazione del processo
            int exitCode = process.waitFor();
            System.out.println("Il processo è terminato con codice: " + exitCode);

        } catch (IOException | InterruptedException e) {
            System.out.println("Errore: " + e.getMessage());
        }
    }
}

Che cosa succede qui?

  • Determiniamo quale sistema operativo è in uso — per scegliere il comando corretto.
  • Creiamo un ProcessBuilder con il comando necessario.
  • Avviamo il processo tramite start().
  • Leggiamo le righe dallo stdout del processo e le stampiamo a schermo.
  • Attendiamo la fine del processo (waitFor()).
  • Stampiamo il codice di uscita (0 = successo, altro = errore).

Esempio 2: avvio di java -version

ProcessBuilder pb = new ProcessBuilder("java", "-version");
try {
    Process process = pb.start();

    // java -version scrive su stderr, quindi leggiamo getErrorStream()
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getErrorStream())
    );
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
    process.waitFor();
} catch (IOException | InterruptedException e) {
    e.printStackTrace();
}

Nota importante: Alcuni comandi (ad esempio, java -version) scrivono le informazioni non sull’output standard (stdout), ma sul flusso di errore (stderr). Perciò a volte è necessario leggere process.getErrorStream().

6. Portabilità: differenze tra Windows e Linux/Mac

  • I comandi e i loro parametri possono differire.
  • I percorsi dei file sono scritti in modo diverso (C:\Temp vs /tmp).
  • Alcuni comandi (ad esempio, ls, cat) esistono solo nei sistemi di tipo Unix, mentre in Windows sono sostituiti da analoghi (dir, type).
  • Su Windows i comandi integrati si avviano solo tramite cmd.exe /c comando.

Esempio di rilevamento del sistema operativo:

String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
    // Windows
} else if (os.contains("mac")) {
    // macOS
} else if (os.contains("nix") || os.contains("nux")) {
    // Linux
}

Consiglio: testate sempre i vostri programmi sui sistemi operativi di destinazione, se puntate alla portabilità multipiattaforma.

7. Tabella: metodi e funzionalità principali di ProcessBuilder

Metodo/campo Scopo Esempio d'uso
new ProcessBuilder(String...)
Creare un processo con comando e argomenti
new ProcessBuilder("ls", "-l")
.directory(File)
Impostare la directory di lavoro
.directory(new File("/tmp"))
.environment()
Ottenere/modificare le variabili d'ambiente
.environment().put("VAR", "value")
.start()
Avviare il processo
Process p = pb.start()
Process.getInputStream()
Ottenere lo stdout del processo
InputStream
Process.getErrorStream()
Ottenere lo stderr del processo
InputStream
Process.getOutputStream()
Ottenere lo stdin del processo
OutputStream
Process.waitFor()
Attendere la fine del processo
int code = p.waitFor()
Process.exitValue()
Ottenere il codice di uscita del processo
int code = p.exitValue()

8. Errori tipici nell'avvio di processi esterni

Errore n. 1: comando non trovato. Se sbagliate il nome del comando o il comando è assente nel sistema, otterrete IOException: Cannot run program .... Ad esempio, provare a eseguire ls su Windows.

Errore n. 2: passaggio errato degli argomenti. Non bisogna unire tutto il comando in un’unica stringa! Corretto: new ProcessBuilder("ping", "google.com"). Errato: new ProcessBuilder("ping google.com").

Errore n. 3: differenze tra OS non considerate. Un comando che funziona alla grande su Linux può non esistere su Windows, e viceversa. Controllate sempre il sistema operativo e adattate il comando.

Errore n. 4: non avete gestito l'output del processo. Se non si legge l’output del processo, questo può “bloccarsi” a causa del buffer pieno. Anche se non avete intenzione di usare l’output — leggetelo e, per esempio, ignoratelo.

Errore n. 5: flussi non chiusi. I flussi del processo vanno chiusi dopo l’uso per evitare perdite di risorse.

Errore n. 6: eccezioni non gestite. L’avvio di un processo esterno è un’operazione rischiosa. Usate sempre try-catch e informate l’utente degli errori.

Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION