Java Runtime.getRuntime (): obtendo saída da execução de um programa de linha de comando

155

Estou usando o tempo de execução para executar comandos do prompt de comando do meu programa Java. No entanto, não estou ciente de como posso obter a saída que o comando retorna.

Aqui está o meu código:

Runtime rt = Runtime.getRuntime();

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

Process proc = rt.exec(commands);

Eu tentei fazer, System.out.println(proc);mas isso não retornou nada. A execução desse comando deve retornar dois números separados por ponto e vírgula. Como eu pude obter isso em uma variável para imprimir?

Aqui está o código que estou usando agora:

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);

Mas não estou conseguindo nada como minha saída, mas quando executo esse comando, ele funciona bem.

user541597
fonte

Respostas:

244

Aqui está o caminho a percorrer:

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);
}

Leia o Javadoc para mais detalhes aqui . ProcessBuilderseria uma boa escolha para usar.

Senthil
fonte
4
@AlbertChen pwd && lsnão é apenas a execução de um único arquivo, quando você faz isso em um shell que executa tanto o /bin/pwde /bin/lsexecutáveis. Se você quiser fazer coisas assim dentro do java, precisará fazer algo assim {"/bin/bash","-c", "pwd && ls"}. Você provavelmente não tem mais a pergunta, mas outras pessoas podem, então pensei em responder.
usar o seguinte código
3
Eu acho que ler as duas correntes deve estar acontecendo simultaneamente porque se, como no seu caso, a saída do stdStream irá preencher o buffer, você não será capaz de ler o fluxo de erro ..
Li3ro
3
Li3ro está parcialmente certo. O programa que você está ouvindo possui um buffer stdoute stderrsaída limitados . Se você não os ouvir simultaneamente, um deles será preenchido enquanto você estiver lendo o outro. O programa que você está ouvindo bloqueará a tentativa de gravação no buffer preenchido, enquanto, por outro lado, o programa bloqueará a tentativa de leitura de um buffer que nunca retornará EOF. Você deve ler os dois fluxos simultaneamente.
Gili
1
@ Gili Então por que o Li3ro é "parcialmente" certo? O Li3ro não está completo e completamente certo? Nesse caso, não entendo por que uma resposta errada está por aqui desde 2011 e por que tem mais de 200 votos positivos ... Isso é confuso.
Andrey Tyukin 12/09/19
2
@AndreyTyukin Você está certo. Todas as respostas atuais são vulneráveis ​​a conflitos. Encorajo-os a fazer um voto negativo para permitir que outras respostas ganhem visibilidade. Publiquei uma nova resposta para sua revisão: stackoverflow.com/a/57949752/14731 . Espero que eu entendi direito ...
Gili
68

Uma maneira mais rápida é esta:

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() : "";
}

Que é basicamente uma versão condensada disso:

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;
}

Sei que esta pergunta é antiga, mas estou postando esta resposta porque acho que isso pode ser mais rápido.

735Tesla
fonte
4
Obrigado pela resposta agradável. Por que "\\ A" é o delimitador?
Gottfried
1
Não me lembro completamente qual era minha lógica quando escrevi isso originalmente. Eu uso essa solução há algum tempo, mas acho que foi porque, \Aem um regex, significa início da string e tive que escapar da barra.
735Tesla
5
"\ A" é o caractere de campainha. "^" é o início de uma sequência no regex e "$" é o final de uma sequência no regex. Este é um personagem que você esperaria não ver. O delimitador padrão é o espaço em branco, de acordo com a documentação do Java, portanto, isso provavelmente cuspiria o resultado completo do comando.
Hank Schultz
11

Além de usar o ProcessBuilderSenthil sugerido, leia e implemente todas as recomendações de When Runtime.exec () não .

Andrew Thompson
fonte
Esse snippet não parece consumir o fluxo de erros padrão (conforme recomendado no artigo vinculado). Também não está usando um ProcessBuildercomo agora recomendado duas vezes. Usando a ProcessBuilder, é possível mesclar os fluxos de saída e erro para facilitar o consumo de ambos de uma só vez.
Andrew Thompson
11

Se o uso já tiver o Apache commons-io disponível no caminho de classe, você poderá usar:

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

Também podemos usar fluxos para obter a saída do 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);
    }
Jackkobec
fonte
7

As respostas @Senthil e @Arend ( https://stackoverflow.com/a/5711150/2268559 ) mencionadas ProcessBuilder. Aqui está o exemplo usando ProcessBuildercom a especificação de variáveis ​​de ambiente e pasta de trabalho para o 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);
nidalpres
fonte
6

No momento da redação deste artigo, todas as outras respostas que incluem código podem resultar em conflitos.

Os processos têm um buffer limitado para stdoute stderrsaída. Se você não os ouvir simultaneamente, um deles será preenchido enquanto você estiver tentando ler o outro. Por exemplo, você pode esperar para ler stdoutenquanto o processo está aguardando para gravar stderr. Você não pode ler do stdoutbuffer porque está vazio e o processo não pode gravar no stderrbuffer porque está cheio. Vocês estão esperando um pelo outro para sempre.

Aqui está uma maneira possível de ler a saída de um processo sem risco de conflitos:

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()
    {
    }
}

A chave é usar o ProcessBuilder.redirectErrorStream(true)que será redirecionado stderrpara o stdoutfluxo. Isso permite que você leia um único fluxo sem ter que alternar entre stdoute stderr. Se você deseja implementar isso manualmente, precisará consumir os fluxos em dois threads diferentes para garantir que nunca bloqueie.

Gili
fonte
Uau! Eu não esperava que você respondesse ao comentário tão rapidamente, muito menos respondesse a essa pergunta eternamente antiga! :) Agora estou pensando em começar uma recompensa. Veremos sua resposta mais tarde. Obrigado!
Andrey Tyukin
1

Se você escreve no Kotlin, pode usar:

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

Adaptado da resposta anterior:

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);
}
Derek Zhu
fonte
0

Praticamente o mesmo que outros trechos nesta página, mas apenas organizando as coisas por uma função , aqui vamos nós ...

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

A função Class:

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;
       }
PYK
fonte
-1

Tente ler InputStreamo tempo de execução:

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);

Você também pode precisar ler o fluxo de erros ( proc.getErrorStream()) se o processo estiver imprimindo uma saída de erro. Você pode redirecionar o fluxo de erros para o fluxo de entrada, se você usar ProcessBuilder.

brahmananda Kar
fonte