Executando Comandos Shell em Java
On Outubro 23, 2021 by adminIntrodução
Neste artigo, vamos dar uma olhada em como podemos aproveitar as classes Runtime
e ProcessBuilder
para executar comandos e scripts shell com Java.
Usamos computadores para automatizar muitas coisas em nossos trabalhos diários. Os administradores de sistema executam muitos comandos o tempo todo, alguns dos quais são muito repetitivos e requerem mudanças mínimas entre execuções.
Este processo também é maduro para automação. Não há necessidade de executar tudo manualmente. Usando Java, podemos executar comandos shell únicos ou múltiplos, executar scripts shell, executar o prompt de terminal/comando, definir diretórios de trabalho e manipular variáveis de ambiente através das classes centrais.
Runtime.exec()
A classe Runtime
em Java é uma classe de alto nível, presente em cada aplicação Java. Através dela, a própria aplicação se comunica com o ambiente em que está em.
Extraindo o tempo de execução associado à nossa aplicação através do método getRuntime()
, podemos usar o método exec()
para executar comandos diretamente ou executar arquivos .bat
/.sh
.
O método exec()
oferece algumas variações sobrecarregadas:
-
public Process exec(String command)
– Executa o comando contido emcommand
em um processo separado. -
public Process exec(String command, String envp)
– Executa ocommand
, com um array de variáveis de ambiente. Eles são fornecidos como um array de Strings, seguindo o formatoname=value
. -
public Process exec(String command, String envp, File dir)
– Executa o comandocommand
, com as variáveis de ambiente especificadas, de dentro do diretóriodir
. -
public Process exec(String cmdArray)
– Executa um comando na forma de um array de Strings. -
public Process exec(String cmdArray, String envp)
– Executa um comando com as variáveis de ambiente especificadas. -
public Process exec(String cmdarray, String envp, File dir)
– Executa um comando, com as variáveis de ambiente especificadas, de dentro do diretóriodir
.
Vale notar que estes processos são executados externamente a partir do interpretador e serão dependentes do sistema.
O que também vale a pena notar é a diferença entre String command
e String cmdArray
. Eles conseguem a mesma coisa. Um command
é quebrado em um array de qualquer maneira, então usar qualquer um destes dois deve render os mesmos resultados.
Cabe a você decidir se exec("dir /folder")
ou exec(new String{"dir", "/folder"}
é o que você gostaria de usar.
Vamos escrever alguns exemplos para ver como estes métodos sobrecarregados diferem um do outro.
Executar um comando a partir de String
Vamos começar com a abordagem mais simples destes três:
Process process = Runtime.getRuntime().exec("ping www.stackabuse.com");
Executar este código irá executar o comando que fornecemos em formato String. No entanto, não vemos nada quando executamos isto.
Para validar se isto rodou corretamente, vamos querer pegar o objeto process
. Vamos usar um BufferedReader
para dar uma olhada no que está acontecendo:
public static void printResults(Process process) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line = ""; while ((line = reader.readLine()) != null) { System.out.println(line); }}
Agora, quando rodarmos este método após o método exec()
, ele deve render algo na linha de:
Pinging www.stackabuse.com with 32 bytes of data:Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Reply from 104.18.57.23: bytes=32 time=21ms TTL=56Ping statistics for 104.18.57.23: Packets: Sent = 4, Received = 4, Lost = 0 (0% loss),Approximate round trip times in milli-seconds: Minimum = 21ms, Maximum = 21ms, Average = 21ms
Cuidado que teremos que extrair a informação do processo das instâncias Process
enquanto passamos por outros exemplos.
Especifique o diretório de trabalho
Se você quiser executar um comando de, digamos, uma determinada pasta, nós faremos algo na linha de:
Process process = Runtime.getRuntime() .exec("cmd /c dir", null, new File("C:\Users\")); //.exec("sh -c ls", null, new File("Pathname")); for non-Windows usersprintResults(process);
Aqui, nós fornecemos o método exec()
com um command
, um null
para novas variáveis de ambiente e um new File()
que é definido como nosso diretório de trabalho.
A adição de cmd /c
antes de um comando como dir
é digno de nota.
Desde que estou a trabalhar no Windows, isto abre os cmd
e /c
executa o comando subsequente. Neste caso, é dir
.
A razão pela qual isto não era obrigatório para o exemplo ping
, mas é obrigatório para este exemplo é agradavelmente respondido por um utilizador SO.
Executar o código anterior resultará em:
Volume in drive C has no label. Volume Serial Number is XXXX-XXXX Directory of C:\Users08/29/2019 05:01 PM <DIR> .08/29/2019 05:01 PM <DIR> ..08/18/2016 09:11 PM <DIR> Default.migrated08/29/2019 05:01 PM <DIR> Public05/15/2020 11:08 AM <DIR> User 0 File(s) 0 bytes 5 Dir(s) 212,555,214,848 bytes free
Vejamos como podemos fornecer o comando anterior em várias partes individuais, em vez de uma única String:
Process process = Runtime.getRuntime().exec( new String{"cmd", "/c", "dir"}, null, new File("C:\Users\")); printResults(process);
Executar este código também resultará:
Volume in drive C has no label. Volume Serial Number is XXXX-XXXX Directory of C:\Users08/29/2019 05:01 PM <DIR> .08/29/2019 05:01 PM <DIR> ..08/18/2016 09:11 PM <DIR> Default.migrated08/29/2019 05:01 PM <DIR> Public05/15/2020 11:08 AM <DIR> User 0 File(s) 0 bytes 5 Dir(s) 212,542,808,064 bytes free
Ultimamente, independentemente da abordagem – usando uma única String ou um array String, o comando que você inserir será sempre decomposto em um array antes de ser processado pela lógica subjacente.
Qual você gostaria de usar resume-se a qual você acha mais legível.
Utilizando variáveis de ambiente
Vamos dar uma olhada em como podemos usar variáveis de ambiente:
Process process = Runtime.getRuntime().exec( "cmd /c echo %var1%", new String{"var1=value1"}); printResults(process);
Podemos fornecer tantas variáveis de ambiente quantas quisermos dentro do array String. Aqui, acabamos de imprimir o valor de var1
usando echo
.
Executar este código retornará:
value1
Executar ficheiros .bat e .sh
Às vezes, é muito mais fácil descarregar tudo num ficheiro e executar esse ficheiro em vez de adicionar tudo programmaticamente.
Dependente do seu sistema operativo, você usaria ficheiros .bat
ou .sh
. Vamos criar um com o conteúdo:
echo Hello World
Então, vamos usar a mesma abordagem de antes:
Process process = Runtime.getRuntime().exec( "cmd /c start file.bat", null, new File("C:\Users\User\Desktop\"));
Isto vai abrir o prompt de comando e executar o arquivo .bat
no diretório de trabalho que nós definimos.
Executar este código certamente resulta em:
Com todas as assinaturas sobrecarregadas exec()
tratadas, vamos dar uma olhada na classe ProcessBuilder
e como podemos executar comandos usando-a.
ProcessBuilder
ProcessBuilder
é o mecanismo subjacente que executa os comandos quando usamos o método Runtime.getRuntime().exec()
:
/** * Executes the specified command and arguments in a separate process with * the specified environment and working directory. *...*/public Process exec(String cmdarray, String envp, File dir) throws IOException { return new ProcessBuilder(cmdarray) .environment(envp) .directory(dir) .start();}
JavaDocs para a classe Runtime
>
Dando uma olhada em como o ProcessBuilder
pega nossa entrada do método exec()
e executa o comando, nos dá uma boa idéia de como usá-lo também.
Aceita um String cmdarray
, e isso é o suficiente para o pôr a correr. Alternativamente, podemos fornecê-lo com argumentos opcionais como o String envp
e File dir
.
Vamos explorar estas opções.
ProcessBuilder: Executando comando a partir de Strings
Em vez de poder fornecer uma única String, como cmd /c dir
, teremos que quebrá-la neste caso. Por exemplo, se quiséssemos listar os arquivos no diretório C:/Users
como antes, faríamos:
ProcessBuilder processBuilder = new ProcessBuilder();processBuilder.command("cmd", "/c", "dir C:\Users");Process process = processBuilder.start();printResults(process);
Para realmente executar um Process
, executamos o comando start()
e atribuímos o valor retornado a uma instância Process
Executar este código renderá:
Volume in drive C has no label. Volume Serial Number is XXXX-XXXX Directory of C:\Users08/29/2019 05:01 PM <DIR> .08/29/2019 05:01 PM <DIR> ..08/18/2016 09:11 PM <DIR> Default.migrated08/29/2019 05:01 PM <DIR> Public05/15/2020 11:08 AM <DIR> User 0 File(s) 0 bytes 5 Dir(s) 212,517,294,080 bytes free
No entanto, esta abordagem não é melhor do que a anterior. O que é útil com a classe ProcessBuilder
é que ela é personalizável. Podemos definir as coisas programmaticamente, não apenas através de comandos.
ProcessBuilder: Especifique o diretório de trabalho
Em vez de fornecer o diretório de trabalho através do comando, vamos configurá-lo programmaticamente:
processBuilder.command("cmd", "/c", "dir").directory(new File("C:\Users\"));
Aqui, definimos o diretório de trabalho como sendo o mesmo de antes, mas movemos essa definição para fora do próprio comando. Executando este código irá fornecer o mesmo resultado que o último exemplo.
ProcessBuilder: Variáveis de Ambiente
Usando métodos ProcessBuilder
s, é fácil recuperar uma lista de variáveis de ambiente na forma de um Map
. Também é fácil definir variáveis de ambiente para que seu programa possa usá-las.
Vamos obter as variáveis de ambiente atualmente disponíveis e então adicionar algumas para uso posterior:
ProcessBuilder processBuilder = new ProcessBuilder();Map<String, String> environmentVariables = processBuilder.environment();environmentVariables.forEach((key, value) -> System.out.println(key + value));
Aqui, empacotamos as variáveis de ambiente retornadas em um Map
e rodamos um forEach()
nele para imprimir os valores para nosso console.
Executar este código renderá uma lista das variáveis de ambiente que você tem na sua máquina:
DriverDataC:\Windows\System32\Drivers\DriverDataHerokuPathE:\HerokuProgramDataC:\ProgramData...
Agora, vamos adicionar uma variável de ambiente a essa lista e usá-la:
environmentVariables.put("var1", "value1");processBuilder.command("cmd", "/c", "echo", "%var1%");Process process = processBuilder.start();printResults(process);
Executar este código renderá:
value1
A partir do momento em que o programa terminar de rodar, esta variável não ficará na lista.
ProcessBuilder: Executando arquivos .bat e .sh
Se você gostaria de executar um arquivo, novamente, nós apenas forneceríamos a instância ProcessBuilder
com as informações necessárias:
processBuilder .command("cmd", "/c", "start", "file.bat") .directory(new File("C:\Users\User\Desktop"));Process process = processBuilder.start();
Executar este código resulta na abertura do prompt de comando e execução do arquivo .bat
>
Conclusão
Neste artigo, nós exploramos exemplos de comandos shell em execução em Java. Usamos as classes Runtime
e ProcessBuilder
para fazer isto.
Usando Java, podemos executar comandos shell simples ou múltiplos, executar scripts shell, rodar o prompt do terminal/comando, definir diretórios de trabalho e manipular variáveis de ambiente através das classes centrais.
Deixe uma resposta