Java Runtime.getRuntime (): ottenere output dall'esecuzione di un programma da riga di comando


155

Sto usando il runtime per eseguire i comandi del prompt dei comandi dal mio programma Java. Tuttavia, non sono a conoscenza di come posso ottenere l'output restituito dal comando.

Ecco il mio codice:

Runtime rt = Runtime.getRuntime();

String[] commands = {"system.exe", "-send" , argument};

Process proc = rt.exec(commands);

Ho provato a farlo System.out.println(proc);ma questo non ha restituito nulla. L'esecuzione di quel comando dovrebbe restituire due numeri separati da un punto e virgola. Come posso ottenere questo in una variabile da stampare?

Ecco il codice che sto usando ora:

String[] commands = {"system.exe", "-get t"};

Process proc = rt.exec(commands);

InputStream stdIn = proc.getInputStream();
InputStreamReader isr = new InputStreamReader(stdIn);
BufferedReader br = new BufferedReader(isr);

String line = null;
System.out.println("<OUTPUT>");

while ((line = br.readLine()) != null)
     System.out.println(line);

System.out.println("</OUTPUT>");
int exitVal = proc.waitFor();
System.out.println("Process exitValue: " + exitVal);

Ma non sto ottenendo nulla come output, ma quando eseguo quel comando da solo funziona benissimo.

Risposte:


244

Ecco la strada da percorrere:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-get t"};
Process proc = rt.exec(commands);

BufferedReader stdInput = new BufferedReader(new 
     InputStreamReader(proc.getInputStream()));

BufferedReader stdError = new BufferedReader(new 
     InputStreamReader(proc.getErrorStream()));

// Read the output from the command
System.out.println("Here is the standard output of the command:\n");
String s = null;
while ((s = stdInput.readLine()) != null) {
    System.out.println(s);
}

// Read any errors from the attempted command
System.out.println("Here is the standard error of the command (if any):\n");
while ((s = stdError.readLine()) != null) {
    System.out.println(s);
}

Leggi il Javadoc per maggiori dettagli qui . ProcessBuildersarebbe una buona scelta da usare.


4
@AlbertChen pwd && lsnon è solo eseguendo un unico file, quando lo fai in un guscio esegue sia la /bin/pwde /bin/lseseguibili. Se vuoi fare cose del genere all'interno di Java devi fare qualcosa del genere {"/bin/bash","-c", "pwd && ls"}. Probabilmente non hai più la domanda, ma altre persone potrebbero, quindi ho pensato di poter rispondere.
735Tesla,

3
Penso che la lettura dei due flussi debba avvenire contemporaneamente perché se, come nel tuo caso, l'output di stdStream riempirà il buffer, non sarai in grado di leggere il flusso di errori ..
Li3ro,

3
Li3ro ha in parte ragione. Il programma che stai ascoltando ha un buffer limitato stdoute un stderroutput. Se non li ascolti contemporaneamente, uno di loro si riempirà mentre leggi l'altro. Il programma che stai ascoltando bloccherà quindi il tentativo di scrivere nel buffer riempito, mentre dall'altra parte il programma bloccherà il tentativo di leggere da un buffer che non tornerà mai più EOF. È necessario leggere contemporaneamente da entrambi i flussi.
Gili,

1
@Gili Allora perché Li3ro è "parzialmente" giusto? Li3ro non è completamente e completamente giusto? In questo caso, non capisco perché una risposta sbagliata sia appesa qui dal 2011 e perché abbia oltre 200 voti positivi ... È confuso.
Andrey Tyukin,

2
@AndreyTyukin Hai ragione. Tutte le risposte correnti sono vulnerabili ai deadlock. Li incoraggio a sottovalutarli per consentire ad altre risposte di ottenere visibilità. Ho pubblicato una nuova risposta per la tua recensione: stackoverflow.com/a/57949752/14731 . Spero di aver capito bene ...
Gili,

68

Un modo più rapido è questo:

public static String execCmd(String cmd) throws java.io.IOException {
    java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\A");
    return s.hasNext() ? s.next() : "";
}

Che è fondamentalmente una versione ridotta di questo:

public static String execCmd(String cmd) throws java.io.IOException {
    Process proc = Runtime.getRuntime().exec(cmd);
    java.io.InputStream is = proc.getInputStream();
    java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\\A");
    String val = "";
    if (s.hasNext()) {
        val = s.next();
    }
    else {
        val = "";
    }
    return val;
}

So che questa domanda è vecchia ma sto pubblicando questa risposta perché penso che potrebbe essere più veloce.


4
Grazie per la bella risposta Perché "\\ A" è il delimitatore?
Gottfried,

1
Non ricordo completamente quale fosse la mia logica quando l'ho scritto in origine. Ho usato questa soluzione per un po ', ma penso che sia stato perché \Ain una regex significa l'inizio della stringa e ho dovuto sfuggire alla barra.
735Tesla

5
"\ A" è il carattere della campana. "^" è l'inizio di una stringa in regex e "$" è la fine di una stringa in regex. Questo è un personaggio che ti aspetteresti di non vedere. Il delimitatore predefinito è lo spazio bianco, secondo la documentazione Java, quindi fare ciò spetterebbe probabilmente l'intero risultato del comando.
Hank Schultz,

11

Oltre ad usare ProcessBuilderSenthil come suggerito, assicurati di leggere e implementare tutti i consigli di When Runtime.exec () no .


Quel frammento non sembra consumare il flusso di errori standard (come raccomandato nell'articolo collegato). Inoltre non sta usando un ProcessBuildercome ora consigliato due volte. Utilizzando a ProcessBuilder, è possibile unire i flussi di output e di errore per semplificare il consumo di entrambi contemporaneamente.
Andrew Thompson,

11

Se l'uso ha già Apache commons-io disponibile sul percorso di classe, è possibile utilizzare:

Process p = new ProcessBuilder("cat", "/etc/something").start();
String stderr = IOUtils.toString(p.getErrorStream(), Charset.defaultCharset());
String stdout = IOUtils.toString(p.getInputStream(), Charset.defaultCharset());

7

Inoltre possiamo usare i flussi per ottenere l'output del comando:

public static void main(String[] args) throws IOException {

        Runtime runtime = Runtime.getRuntime();
        String[] commands  = {"free", "-h"};
        Process process = runtime.exec(commands);

        BufferedReader lineReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        lineReader.lines().forEach(System.out::println);

        BufferedReader errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        errorReader.lines().forEach(System.out::println);
    }

7

@Senthil e @Arend answer ( https://stackoverflow.com/a/5711150/2268559 ) menzionati ProcessBuilder. Ecco l'esempio usando ProcessBuildercon specificare le variabili d'ambiente e la cartella di lavoro per il comando:

    ProcessBuilder pb = new ProcessBuilder("ls", "-a", "-l");

    Map<String, String> env = pb.environment();
    // If you want clean environment, call env.clear() first
    //env.clear();
    env.put("VAR1", "myValue");
    env.remove("OTHERVAR");
    env.put("VAR2", env.get("VAR1") + "suffix");

    File workingFolder = new File("/home/user");
    pb.directory(workingFolder);

    Process proc = pb.start();

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));

    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null)
        System.out.println(s);

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null)
        System.out.println(s);

6

Al momento della stesura di questo documento, tutte le altre risposte che includono codice possono causare deadlock.

I processi hanno un buffer limitato per stdoute stderroutput. Se non li ascolti contemporaneamente, uno di loro si riempirà mentre stai provando a leggere l'altro. Ad esempio, potresti essere in attesa di leggere stdoutmentre il processo è in attesa di scrivere stderr. Non è possibile leggere dal stdoutbuffer perché è vuoto e il processo non può scrivere nel stderrbuffer perché è pieno. Vi state aspettando per sempre.

Ecco un modo possibile per leggere l'output di un processo senza il rischio di deadlock:

public final class Processes
{
    private static final String NEWLINE = System.getProperty("line.separator");

    /**
     * @param command the command to run
     * @return the output of the command
     * @throws IOException if an I/O error occurs
     */
    public static String run(String... command) throws IOException
    {
        ProcessBuilder pb = new ProcessBuilder(command).redirectErrorStream(true);
        Process process = pb.start();
        StringBuilder result = new StringBuilder(80);
        try (BufferedReader in = new BufferedReader(new InputStreamReader(process.getInputStream())))
        {
            while (true)
            {
                String line = in.readLine();
                if (line == null)
                    break;
                result.append(line).append(NEWLINE);
            }
        }
        return result.toString();
    }

    /**
     * Prevent construction.
     */
    private Processes()
    {
    }
}

La chiave è usare ProcessBuilder.redirectErrorStream(true)che reindirizzerà stderrnel stdoutflusso. Ciò ti consente di leggere un singolo flusso senza dover alternare tra stdoute stderr. Se vuoi implementarlo manualmente, dovrai consumare i flussi in due thread diversi per assicurarti di non bloccare mai.


Oh wow! Non mi aspettavo che avresti risposto al commento così in fretta, figuriamoci rispondere a questa domanda eternamente vecchia! :) Ora sto pensando di iniziare una taglia. Daremo un'occhiata alla tua risposta più tardi. Grazie!
Andrey Tyukin,

1

Se scrivi su Kotlin, puoi usare:

val firstProcess = ProcessBuilder("echo","hello world").start()
val firstError = firstProcess.errorStream.readBytes().decodeToString()
val firstResult = firstProcess.inputStream.readBytes().decodeToString()

0

Adattato dalla risposta precedente:

public static String execCmdSync(String cmd, CmdExecResult callback) throws java.io.IOException, InterruptedException {
    RLog.i(TAG, "Running command:", cmd);

    Runtime rt = Runtime.getRuntime();
    Process proc = rt.exec(cmd);

    //String[] commands = {"system.exe", "-get t"};

    BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));

    StringBuffer stdOut = new StringBuffer();
    StringBuffer errOut = new StringBuffer();

    // Read the output from the command:
    System.out.println("Here is the standard output of the command:\n");
    String s = null;
    while ((s = stdInput.readLine()) != null) {
        System.out.println(s);
        stdOut.append(s);
    }

    // Read any errors from the attempted command:
    System.out.println("Here is the standard error of the command (if any):\n");
    while ((s = stdError.readLine()) != null) {
        System.out.println(s);
        errOut.append(s);
    }

    if (callback == null) {
        return stdInput.toString();
    }

    int exitVal = proc.waitFor();
    callback.onComplete(exitVal == 0, exitVal, errOut.toString(), stdOut.toString(), cmd);

    return stdInput.toString();
}

public interface CmdExecResult{
    void onComplete(boolean success, int exitVal, String error, String output, String originalCmd);
}

0

Più o meno lo stesso degli altri frammenti di questa pagina ma organizzando semplicemente le cose su una funzione , eccoci qui ...

String str=shell_exec("ls -l");

La funzione di classe:

public String shell_exec(String cmd)
       {
       String o=null;
       try
         {
         Process p=Runtime.getRuntime().exec(cmd);
         BufferedReader b=new BufferedReader(new InputStreamReader(p.getInputStream()));
         String r;
         while((r=b.readLine())!=null)o+=r;
         }catch(Exception e){o="error";}
       return o;
       }

-1

Prova a leggere InputStreamil runtime:

Runtime rt = Runtime.getRuntime();
String[] commands = {"system.exe", "-send", argument};
Process proc = rt.exec(commands);
BufferedReader br = new BufferedReader(
    new InputStreamReader(proc.getInputStream()));
String line;
while ((line = br.readLine()) != null)
    System.out.println(line);

Potrebbe anche essere necessario leggere il flusso di errori ( proc.getErrorStream()) se il processo sta stampando un output degli errori. È possibile reindirizzare il flusso di errori al flusso di input se lo si utilizza ProcessBuilder.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.