Executando comandos do shell

PythonBeginner
Pratique Agora

Introdução

No trabalho de operações, mesmo ao escrever código Python, muitas vezes você ainda precisa chamar comandos do sistema que já existem. Por isso, é importante entender as ferramentas do Python para executar processos.

O módulo subprocess permite que o Python inicie outros programas, capture a saída deles e verifique com segurança se foram executados com sucesso ou falharam. Essa é a forma padrão de conectar código Python a comandos do shell.

Neste laboratório, você aprenderá a:

  1. Importar o módulo subprocess.
  2. Executar um comando simples do Linux (ls -l) usando Python.
  3. Capturar a saída do comando para processá-la posteriormente.
  4. Decodificar a saída de bytes para uma string legível.
  5. Tratar erros de forma adequada quando um comando falhar.

Você trabalhará no diretório ~/project em um ambiente Linux.

Importar o subprocess

Nesta etapa, você criará um script Python e importará o módulo necessário para interagir com o shell do sistema.

O módulo subprocess faz parte da biblioteca padrão do Python, portanto você não precisa instalar nada adicional. Ele permite que seu script Python se comunique com o sistema operacional subjacente.

Essa é a ponte padrão entre o código Python e as ferramentas Unix existentes. No trabalho de operações, essa ponte é importante porque raramente você substitui todos os comandos do shell de uma só vez. Com mais frequência, o Python atua como coordenador: chama as ferramentas do sistema, captura os resultados e adiciona uma lógica de nível mais alto.

Primeiro, verifique se você está no diretório correto:

cd ~/project

Agora, crie um novo arquivo Python chamado list_files.py usando o editor nano ou o método de sua preferência:

nano list_files.py

Adicione a linha a seguir no início do arquivo para importar o módulo:

import subprocess

print("Module imported successfully")

Salve o arquivo e saia do editor. No nano, pressione Ctrl+X, depois Y e, por fim, Enter.

Agora, execute o script para verificar se tudo está funcionando corretamente:

python3 list_files.py

Você deverá ver a mensagem "Module imported successfully".

Executar um comando

Nesta etapa, você modificará o script para executar de fato um comando do Linux. Usaremos a função subprocess.run(), introduzida no Python 3.5 como a abordagem recomendada para chamar subprocessos.

Quando o Python inicia outro programa, ele cria um processo separado. Esse processo filho tem seu próprio código de saída, saída padrão e saída de erro, assim como um comando executado diretamente no shell. Entender essa separação entre processos facilita bastante o tratamento de erros nas etapas seguintes.

Abra list_files.py novamente:

nano list_files.py

Altere o arquivo para que ele fique assim. Usaremos o comando ls -l para listar os arquivos do diretório atual com detalhes:

import subprocess

## Run the 'ls -l' command
## The command and its arguments are passed as a list of strings
subprocess.run(["ls", "-l"])

Observe que passamos o comando como uma lista: ["ls", "-l"]. Isso é mais seguro e mais claro do que passar uma única string, pois evita problemas com injeção no shell e com espaços nos nomes de arquivos.

Salve o arquivo e saia do editor.

Agora, execute o script:

python3 list_files.py

Você deverá ver a saída do comando ls -l exibida diretamente no terminal. Isso acontece porque, por padrão, subprocess.run envia a saída do comando para a saída padrão, que é a tela.

Capturar a saída

Na etapa anterior, a saída foi exibida diretamente na tela. No entanto, em scripts de automação, normalmente você quer capturar essa saída em uma variável para que o código Python possa processá-la, por exemplo, para analisar nomes de arquivos ou verificar erros.

Essa é a diferença entre apenas executar um comando e integrá-lo a um programa. Depois que a saída é capturada como dados, seu script pode tomar decisões com base nela, em vez de deixar o resultado apenas no terminal para uma pessoa ler.

Para fazer isso, precisamos usar o argumento capture_output=True.

Abra list_files.py novamente:

nano list_files.py

Atualize o código para capturar o resultado:

import subprocess

## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)

## Print the captured result object
print(result)

Salve o arquivo e saia do editor.

Execute o script:

python3 list_files.py

Você verá uma saída parecida com esta:

CompletedProcess(args=['ls', '-l'], returncode=0, stdout=b'total 0\n-rw-rw-r-- 1 labex labex 0 ...', stderr=b'')

Esse é um objeto CompletedProcess. Observe a parte stdout=b'...'. O b significa bytes. Isso indica que o Python capturou os dados brutos exatamente como o computador os vê, e não como texto legível, ou seja, uma string. Na próxima etapa, converteremos esses dados em texto legível.

Decodificar a saída

Como a saída foi capturada como bytes, precisamos decodificá-la para uma string Python padrão em UTF-8, tornando-a legível e utilizável.

A diferença entre bytes e texto é fundamental no Python. Em última análise, os computadores movimentam bytes brutos, enquanto as pessoas normalmente querem trabalhar com texto. Muitos erros confusos em automação acontecem porque você se esquece de qual desses formatos está usando.

Abra list_files.py mais uma vez:

nano list_files.py

Atualize o código para acessar o atributo stdout e decodificá-lo:

import subprocess

## Run the command and capture the output
result = subprocess.run(["ls", "-l"], capture_output=True)

## Decode the bytes to a string
output_string = result.stdout.decode("utf-8")

## Print the clean string
print("--- Command Output ---")
print(output_string)

Salve o arquivo e saia do editor.

Execute o script:

python3 list_files.py

Agora você deverá ver a saída limpa e formatada do comando ls -l, exatamente como se tivesse executado o comando diretamente no shell, mas desta vez ela está sendo exibida pelo Python.

Observação: como alternativa, você pode passar text=True ou universal_newlines=True para subprocess.run() e deixar que o Python faça a decodificação automaticamente. Ainda assim, é importante entender o método .decode() para trabalhar com dados binários.

Tratar erros

O que acontece quando o comando falha? Por exemplo, se você tentar listar um diretório que não existe. Bons scripts precisam tratar esses erros.

Em sistemas do tipo Unix, a falha de um comando geralmente é comunicada por meio de um status de saída diferente de zero. subprocess.run(..., check=True) converte essa convenção do sistema operacional em uma exceção Python, permitindo que seu script reaja usando o fluxo de controle normal do Python.

Vamos criar um novo script chamado handle_error.py para demonstrar isso.

nano handle_error.py

Adicione o código a seguir. Tentaremos listar um diretório inexistente chamado /nonexistent. Também usaremos check=True, que instrui o Python a gerar uma exceção CalledProcessError quando o comando retornar um código de saída diferente de zero, indicando uma falha.

import subprocess

try:
    ## Try to list a directory that does not exist
    ## check=True raises an exception if the command fails
    subprocess.run(["ls", "/nonexistent"], check=True, capture_output=True)

except subprocess.CalledProcessError as e:
    print("An error occurred!")
    print(f"Return code: {e.returncode}")
    ## We can also print the error message generated by the command (stderr)
    print(f"Error message: {e.stderr.decode('utf-8')}")

Salve o arquivo e saia do editor.

Execute o script:

python3 handle_error.py

Você deverá ver uma saída indicando que o erro foi capturado:

An error occurred!
Return code: 2
Error message: ls: cannot access '/nonexistent': No such file or directory

Se não usássemos o bloco try-except, o programa falharia com um traceback extenso. Essa abordagem permite que seu script detecte falhas e decida o que fazer em seguida, como registrar o erro, tentar novamente ou sair de forma limpa.

Resumo

Neste laboratório, você aprendeu os fundamentos da execução de comandos do shell usando o módulo subprocess do Python. Você praticou:

  • Importar o módulo subprocess.
  • Executar comandos usando subprocess.run().
  • Capturar a saída padrão usando capture_output=True.
  • Decodificar a saída em bytes para strings legíveis.
  • Tratar falhas de comandos usando check=True e blocos try-except.

Essas habilidades são essenciais para criar scripts de automação, ferramentas de implantação e utilitários de gerenciamento de sistemas em Python.