Ricerca…


Osservazioni

Si noti che l'API raccomanda che, a partire dalla versione 1.5, il modo preferito per creare un processo sia l'utilizzo di ProcessBuilder.start() .

Un'altra osservazione importante è che il valore di uscita prodotto da waitFor dipende dal programma / script in esecuzione. Ad esempio, i codici di uscita prodotti da calc.exe sono diversi da notepad.exe .

Esempio semplice (versione Java <1.5)

Questo esempio chiamerà il calcolatore di Windows. È importante notare che il codice di uscita varierà in base al programma / script che viene chiamato.

package process.example;

import java.io.IOException;

public class App {

    public static void main(String[] args) {
        try {
            // Executes windows calculator
            Process p = Runtime.getRuntime().exec("calc.exe");

            // Wait for process until it terminates
            int exitCode = p.waitFor();

            System.out.println(exitCode);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Utilizzando la classe ProcessBuilder

La classe ProcessBuilder semplifica l'invio di un comando tramite la riga di comando. Tutto ciò che serve è un elenco di stringhe che compongono i comandi da inserire. Basta chiamare il metodo start () sull'istanza ProcessBuilder per eseguire il comando.

Se hai un programma chiamato Add.exe che prende due argomenti e li aggiunge, il codice sarebbe simile a questo:

List<String> cmds = new ArrayList<>();
cmds.add("Add.exe"); //the name of the application to be run
cmds.add("1"); //the first argument
cmds.add("5"); //the second argument

ProcessBuilder pb = new ProcessBuilder(cmds);

//Set the working directory of the ProcessBuilder so it can find the .exe
//Alternatively you can just pass in the absolute file path of the .exe
File myWorkingDirectory = new File(yourFilePathNameGoesHere);
pb.workingDirectory(myWorkingDirectory);

try {
    Process p = pb.start(); 
} catch (IOException e) {
    e.printStackTrace();
}

Alcune cose da tenere a mente:

  • L'array di comandi deve essere tutto un array di stringhe
  • I comandi devono essere nell'ordine (nell'array) che sarebbero se avessi fatto la chiamata al programma nella linea di comando stessa (cioè il nome dell'exe non può andare dopo il primo argomento
  • Quando si imposta la directory di lavoro è necessario passare in un oggetto File e non solo il nome del file come una stringa

Blocco contro chiamate non bloccanti

In generale quando si effettua una chiamata alla riga di comando, il programma invierà il comando e quindi continuerà la sua esecuzione.

Tuttavia potresti voler aspettare che il programma chiamato finisca prima di continuare la tua esecuzione (ad esempio, il programma chiamato scriverà i dati su un file e il tuo programma avrà bisogno di accedere a quei dati).

Questo può essere fatto facilmente chiamando il metodo waitFor() Process restituita.

Esempio di utilizzo:

//code setting up the commands omitted for brevity...

ProcessBuilder pb = new ProcessBuilder(cmds);

try {
    Process p = pb.start();
    p.waitFor();
} catch (IOException e) {
    e.printStackTrack();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//more lines of code here...

ch.vorburger.exec

L'avvio di processi esterni da Java utilizzando direttamente l'API java.lang.ProcessBuilder non elaborata può risultare un po 'macchinoso. La libreria Apache Commons Exec lo rende un po 'più semplice. La libreria ch.vorburger.exec si estende ulteriormente su Commons Exec per renderlo veramente conveniente:

 ManagedProcess proc = new ManagedProcessBuilder("path-to-your-executable-binary")
     .addArgument("arg1")
     .addArgument("arg2")
     .setWorkingDirectory(new File("/tmp"))
     .setDestroyOnShutdown(true)
     .setConsoleBufferMaxLines(7000)
     .build();

proc.start();
int status = proc.waitForExit();
int status = proc.waitForExitMaxMsOrDestroy(3000);
String output = proc.getConsole();

proc.startAndWaitForConsoleMessageMaxMs("started!", 7000);
// use service offered by external process...
proc.destroy();

Pitfall: Runtime.exec, Process e ProcessBuilder non capiscono la sintassi della shell

I metodi Runtime.exec(String ...) e Runtime.exec(String) consentono di eseguire un comando come processo esterno 1 . Nella prima versione, si fornisce il nome del comando e gli argomenti del comando come elementi separati dell'array di stringhe e il runtime Java richiede al sistema di runtime del sistema operativo di avviare il comando esterno. La seconda versione è ingannevolmente facile da usare, ma presenta alcune insidie.

Prima di tutto, ecco un esempio di utilizzo di exec(String) usato in modo sicuro:

Process p = Runtime.exec("mkdir /tmp/testDir");
p.waitFor();
if (p.exitValue() == 0) {
    System.out.println("created the directory");
}

Spazi nei nomi dei percorsi

Supponiamo di generalizzare l'esempio sopra in modo da poter creare una directory arbitraria:

Process p = Runtime.exec("mkdir " + dirPath);
// ...

Questo in genere funziona, ma fallirà se dirPath è (ad esempio) "/ home / utente / Documenti". Il problema è che exec(String) divide la stringa in un comando e argomenti semplicemente cercando spazi bianchi. La stringa di comando:

"mkdir /home/user/My Documents"

sarà diviso in:

"mkdir", "/home/user/My", "Documents"

e questo causerà il fallimento del comando "mkdir" perché prevede un argomento, non due.

Di fronte a questo, alcuni programmatori cercano di aggiungere citazioni attorno al percorso. Questo non funziona neanche:

"mkdir \"/home/user/My Documents\""

sarà diviso in:

"mkdir", "\"/home/user/My", "Documents\""

I caratteri di doppia virgoletta aggiuntivi che sono stati aggiunti nel tentativo di "quotare" gli spazi sono trattati come qualsiasi altro carattere non di uno spazio bianco. In effetti, tutto ciò che citiamo o sfuggiamo agli spazi fallirà.

Il modo di affrontare questo particolare problema è usare il sovraccarico exec(String ...) .

Process p = Runtime.exec("mkdir", dirPath);
// ...

Questo funzionerà se dirpath include caratteri di spaziatura perché questo overload di exec non tenta di dividere gli argomenti. Le stringhe vengono passate alla exec sistema exec sistema operativo così com'è.

Reindirizzamento, pipeline e altra sintassi della shell

Supponiamo di voler reindirizzare l'input o l'output di un comando esterno o eseguire una pipeline. Per esempio:

Process p = Runtime.exec("find / -name *.java -print 2>/dev/null");

o

Process p = Runtime.exec("find source -name *.java | xargs grep package");

(Il primo esempio elenca i nomi di tutti i file Java nel file system e il secondo stampa le istruzioni del package 2 nei file Java nell'albero "origine".)

Questi non funzioneranno come previsto. Nel primo caso, il comando "trova" verrà eseguito con "2> / dev / null" come argomento del comando. Non sarà interpretato come un reindirizzamento. Nel secondo esempio, il carattere pipe ("|") e le opere successive verranno dati al comando "find".

Il problema qui è che i metodi exec e ProcessBuilder non capiscono alcuna sintassi della shell. Ciò include reindirizzamenti, pipeline, espansione variabile, globbing e così via.

In alcuni casi (ad esempio, il reindirizzamento semplice) è possibile ottenere facilmente l'effetto desiderato utilizzando ProcessBuilder . Tuttavia, questo non è vero in generale. Un approccio alternativo è quello di eseguire la riga di comando in una shell; per esempio:

Process p = Runtime.exec("bash", "-c", 
                         "find / -name *.java -print 2>/dev/null");

o

Process p = Runtime.exec("bash", "-c", 
                         "find source -name \\*.java | xargs grep package");

Notate che nel secondo esempio, dovevamo sfuggire al carattere jolly ("*") perché vogliamo che il carattere jolly venga interpretato da "find" piuttosto che dalla shell.

I comandi incorporati della shell non funzionano

Supponiamo che i seguenti esempi non funzionino su un sistema con una shell simile a UNIX:

Process p = Runtime.exec("cd", "/tmp");     // Change java app's home directory

o

Process p = Runtime.exec("export", "NAME=value");  // Export NAME to the java app's environment

Ci sono un paio di motivi per cui questo non funzionerà:

  1. I comandi "cd" e "export" sono comandi incorporati nella shell. Non esistono come eseguibili distinti.

  2. Perché i builtin della shell facciano ciò che dovrebbero fare (ad esempio cambiano la directory di lavoro, aggiornano l'ambiente), hanno bisogno di cambiare il luogo in cui si trova lo stato. Per un'applicazione normale (inclusa un'applicazione Java) lo stato è associato al processo dell'applicazione. Ad esempio, il processo figlio che eseguiva il comando "cd" non poteva cambiare la directory di lavoro del suo processo "java" genitore. Allo stesso modo, un processo di exec 'd non può cambiare la directory di lavoro per un processo che lo segue.

Questo ragionamento si applica a tutti i comandi incorporati della shell.


1 - È possibile utilizzare ProcessBuilder , ma ciò non è pertinente al punto di questo esempio.

2 - Questo è un po 'approssimativo e pronto ... ma ancora una volta, i difetti di questo approccio non sono rilevanti per l'esempio.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow