Armadilhas no uso de Runtime.exec()

Vamos mostrar primeiro algumas armadilhas que temos que evitar antes de executar algum comando usando Runtime.exec()


            - IllegalThreadStateException
        

A primeira armadilha é a exceção “IllegalThreadStateException”. A primeira coisa que fazemos quando vamos testar um método de uma API é codificar o método da forma mais obvia possível. Tendo isso em vista, vamos mostrar a primeira armadilha na execução do método exec(). Para podermos ver o valor que nosso processo retorna usamos o método exitValue() da classe Process. No exemplo abaixo vamos tentar listar os arquivos do diretório corrente (“dir” no Windows, e “ls” no Linux).

Listagem 1. ListagemComErro

            String osName = System.getProperty("os.name" );
            String[] cmd = new String[3];
            if( osName.equals( "Windows 2000" ) ){
                 cmd[0] = "cmd.exe" ;
                 cmd[1] = "/C" ;
                 cmd[2] = "dir /w";
            }else if( osName.equals( "Linux" ) ){
                 cmd[0] = "/bin/sh" ;
                 cmd[1] = "ls" ;
              cmd[2] = "-la";
         }else{
              //faz alguma coisa                
         }
         Process p = Runtime.getRuntime().exec(cmd);
         int exitVal = p.exitValue();
         System.out.println("Process exitValue: " + exitVal);
        

Explicação da Listagem 1:

  • Linha 1: Pagamos o nome do sistema operacional utilizando System.getProperty(“osname”). Mais propriedades podem ser encontradas no Javadoc do seu SDK (links na parte de referências)
  • Linha 2: Cria um array onde serão colocados o comando a ser executado e os argumentos para o comando
  • Linha 3 - 6: Se o sistema operacional for o windows 2000 ele executa o comando “cmd” (interpretador de comandos do ms-dos no windows 2000) e passa os argumentos “/C” (command line) e “dir /w” (lista diretórios)
  • Linhas 7-10: Se o sistema operacional for linux ele executa o comando “/bin/sh” (interpretador de comandos do linux, Bourne Again SHell - Bash ) e passa os argumentos “ls” (listar) e “-la” (todos arquivos e diretórios)
  • Linha 14: Cria um sub processo para que a JVM consiga executar os comandos passados (cmd)
  • Linha 15: Cria um int que recebera o valor de status de saída da aplicação
  • Linha 16: Mostra no console o valor de saida

Retorno:

Ao executarmos o código, recebemos a seguinte mensagem de erro:


            java.lang.IllegalThreadStateException: process has not exited

         at java.lang.ProcessImpl.exitValue(Native Method)

         at tests.ListagemComErro.main(ListagemComErro.java:10)
        

Explicação:

Se um processo não terminou ainda de executar, a chamada ao método exitValue() vai causar uma exceção do tipo “IllegalThreadStateException”, e é por isso que o programa não funcionou. Se você precisar saber o status de retorno do fim da execução de um programa externo, você deve utilizar o método waitFor(), que retorna o valor de status de termino do programa apenas após ele ser realmente finalizado. A única possibilidade de se usar exitValue() ao invés de waitFor(), é quando você não quer que seu programa fique parado esperando o termino de um processo. Nesse caso, você pode fazer algo como passar um parâmetro boolean chamado wait dentro do método exitValue() para determinar se a thread em execução deve ou não esperar pelo termino da execução.

Resumindo, ou você coloca a chamada ao método exitValue() dentro de um bloco try/catch e cuida da exceção “IllegalThreadStateException”, ou você espera o processo terminar de executar.

Execução sem output

Listagem 2. ListagemComErro2

            String osName = System.getProperty("os.name" );
            String[] cmd = new String[3];
            public class ListagemComErro2
            {
                public static void main(String args[])
                {
                    try
                    {
                         if( osName.equals( "Windows 2000" ) ){
                           cmd[0] = "cmd.exe" ;
                          cmd[1] = "/C" ;
                          cmd[2] = "dir /w";
                         }else if( osName.equals( "Linux" ) ){           
                           cmd[0] = "/bin/sh" ;
                           cmd[1] = "ls" ;
                          cmd[2] = "-la";
                     }else{
                          //faz alguma coisa
                     }
                      Process p = Runtime.getRuntime().exec(cmd);
                      int exitVal = p.waitFor();
                      System.out.println("Process exitValue: " + exitVal);
                 } catch (Throwable t){
                     t.printStackTrace();
                   }
             }
         }
        

Explicação da Listagem 2:

Como já expliquei a maioria das linhas na listagem anterior, aqui listarei somente as linhas que estão diferentes.

  • Linha 21: Utilizamos waitFor() para sabermos o retorno do status do termino da execução
  • Linha 23 - 25: Colocamos uma clausula catch para o caso de alguma exceção ser disparada (como por exemplo uma “IllegalThreadStateException”)

Retorno:

A execução do programa ‘ListagemComErro2’ não produz saída nenhuma, o programa trava e não completa nunca.

Explicação:

Como cada plataforma disponibiliza um tamanho limitado de buffer para os fluxos de entrada e saída padrões, uma falha na escrita do fluxo de entrada ou de leitura do fluxo de saída de um subprocesso pode fazer com que este subprocesso trave, ou, ate mesmo, ocasione um ‘deadlock’ (programa termina de forma anormal).

Se você procurar na documentação do SDK, você verá que isto é mencionado lá, porém, não nos dizem como fazer para manipular tais fluxos.


            - Erro de execução com Status de Saida igual a 2 (“arquivo não encontrado”)
        
Listagem 3. ListagemComErro3

                        import java.util.*;
            import java.io.*;
            public class ListagemComErro3
            {
                public static void main(String args[])
                {
                    try
                    {
                        Process p = Runtime.getRuntime().exec(“dir.exe”);
                     InputStream stdin = p.getInputStream();
                     BufferedReader br = new BufferedReader( new InputStreamReader(stdin) );
                     String line = null;
                     while ( (line = br.readLine()) != null)
                             System.out.println(line);
                     int exitVal = p.waitFor();            
                     System.out.println("Process exitValue: " + exitVal);
                 } catch (Throwable t)
                   {
                         t.printStackTrace();
                   }
             }
         }
        

explicação da Listagem 3:

  • Linha 9: Cria um sub processo e executa o comando “dir”
  • Linha 10: Cria um fluxo de entrada e associa ele ao fluxo de entrada do sub processo
  • Linha 11: Cria um leitor com buffer a partir de um leitor de fluxo de entrada que é criado a partir do fluxo de entrada criado na Linha 10
  • Linha 12: Cria a String “line” onde o programa armazenara a linha lida através do leitor criado na linha 11
  • Linha 13 - 14: Lê o conteúdo do leitor criado na linha 11 e o coloca na variável “line” criada na linha 12 enquanto não receber um valor null (que indica o fim do fluxo) e depois imprime no console o valor da linha lida
  • Linha 15 - 16: Recebe o valor do status do fim da execução do sub processo e mostra no console
  • Linha 17 - 20: Pega uma exceção do tipo “Throwable” caso ela ocorra

Retorno:

Ao executarmos o código, recebemos a seguinte mensagem de erro:


            java.io.IOException: CreateProcess: dir error=2
        at java.lang.Win32Process.create(Native Method)
        at java.lang.Win32Process.(Unknown Source)
        at java.lang.Runtime.execInternal(Native Method)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at java.lang.Runtime.exec(Unknown Source)
        at ListagemComErro3.main(ListagemComErro3.java:12)
        

Explicação:

Um status de saída com valor 2 significa “arquivo não encontrado”, que neste caso, indica que o arquivo executado por nosso programa (dir.exe) não pode ser encontrado. Isso ocorre porque o diretório command, onde ficam esses comandos de sistema, são parte do interpretador de comandos do Windows e não um programa executável independente. Para executar esse tipo de comando, você tem que passar como comando principal “cmd.exe” e o parâmetro “/C” e só então passar o comando “dir.exe” como parâmetro. A mesma regra se aplica ao linux, antes de executar um comando você tem que colocar como comando principal “/bin/sh” e depois o comando a ser executado “ls” e seus parâmetros “-la”.caso haja necessidade.