Domine Subprocessos em Python: Guia Completo com Exemplos!

Explorando o Universo dos Subprocessos com Python

Os subprocessos abrem uma porta para uma interação mais profunda com o sistema operacional do seu computador.

Constantemente, nossos computadores executam uma variedade de subprocessos. Inclusive, ao ler este texto, diversos processos estão ativos, como o gerenciador de rede ou o próprio navegador.

É fascinante como cada ação que realizamos no computador, desde abrir um programa até executar um simples script, envolve a criação de um subprocesso. Até mesmo um “Olá, Mundo!” em Python desencadeia essa mecânica.

O conceito de subprocesso pode parecer um mistério, mesmo para programadores experientes. Este artigo visa desvendar esse conceito e apresentar como utilizar a biblioteca padrão subprocess do Python.

Ao final desta leitura, você deverá:

  • Compreender a essência de um subprocesso.
  • Dominar os fundamentos da biblioteca subprocess do Python.
  • Aprimorar suas habilidades em Python com exemplos práticos.

Vamos iniciar essa jornada!

Desvendando o Conceito de Subprocesso

De forma simples, um subprocesso é um processo computacional que é iniciado por outro processo.

Imagine os subprocessos como uma árvore, onde cada processo pai gera processos filhos que são executados em sequência. Essa ideia pode ser um pouco complexa, mas uma representação visual pode ajudar.

Existem várias maneiras de observar os processos em execução no seu computador. No ambiente UNIX (Linux e MAC), por exemplo, temos o htop, uma ferramenta interativa para monitorar processos.

O modo de visualização em árvore do htop, ativado pela tecla F5, é excelente para examinar a hierarquia dos subprocessos.

Ao analisarmos a coluna de comandos, podemos perceber a estrutura dos processos em execução no computador.

Tudo se inicia com o comando /sbin/init, responsável por inicializar todos os processos. A partir dele, outros processos são iniciados, como o xfce4-screenshoter e o xfce4-terminal, que por sua vez, podem gerar mais subprocessos.

No Windows, temos o popular Gerenciador de Tarefas, uma ferramenta útil para finalizar programas que travam.

Com esse entendimento, podemos avançar para a implementação de subprocessos em Python.

Subprocessos em Ação com Python

Em Python, um subprocesso representa uma tarefa que o script delega ao sistema operacional.

A biblioteca subprocess permite iniciar e controlar esses subprocessos diretamente do Python, trabalhando com a entrada padrão (stdin), a saída padrão (stdout) e os códigos de retorno.

Essa biblioteca é uma parte integrante do Python padrão, portanto não necessita de instalação extra via PIP.

Para utilizar os subprocessos em Python, basta importar o módulo:

import subprocess

# E aqui, começamos a usar o módulo...

Observação: este artigo requer Python 3.5 ou superior.

Para verificar sua versão do Python, execute:

❯ python --version
Python 3.9.5 # Exemplo

Se a versão exibida for 2.x, use o comando:

python3 --version

A biblioteca subprocess possibilita interagir com o sistema operacional, executando comandos diretamente do interpretador Python.

Isso significa que você pode realizar praticamente qualquer operação que o sistema operacional permitir (e sem acidentalmente apagar seu diretório raiz 😅).

Vamos ver isso em prática, criando um script que liste os arquivos do diretório atual.

Seu Primeiro Subprocesso em Python

Comece criando um arquivo chamado `list_dir.py`, onde vamos experimentar a listagem de arquivos:

touch list_dir.py

Abra o arquivo e insira o seguinte código:

import subprocess 

subprocess.run('ls')

Primeiramente, o módulo subprocess é importado. Em seguida, a função `run` é utilizada para executar o comando passado como argumento.

Essa função, introduzida no Python 3.5, simplifica o uso da subprocess.Popen. A função `subprocess.run` executa um comando e aguarda sua conclusão, ao contrário da `Popen`, que permite controlar a comunicação posteriormente.

No terminal, o comando `ls` é usado em sistemas UNIX para listar arquivos e diretórios. Portanto, ao executar esse código, você verá os arquivos e diretórios no local do script.

❯ python list_dir.py
example.py  LICENSE  list_dir.py  README.md

Observação: em ambientes Windows, comandos diferentes devem ser utilizados. Por exemplo, no lugar de “ls”, use “dir”.

Este exemplo é simples, mas serve como uma introdução ao poder que a linha de comando oferece. Vamos explorar como passar argumentos para o shell com subprocess.

Para listar arquivos ocultos (aqueles que começam com um ponto) e exibir seus metadados, utilize o seguinte código:

import subprocess

# subprocess.run('ls')  # Comando simples

subprocess.run('ls -la', shell=True)

Aqui, o comando é executado como uma string, com o argumento `shell=True`. Isso significa que o shell é invocado antes do subprocesso, e o comando é interpretado diretamente por ele.

Entretanto, usar `shell=True` apresenta desvantagens, incluindo possíveis brechas de segurança. A documentação oficial contém mais informações sobre isso.

A maneira mais segura de passar comandos para a função `run` é utilizar uma lista, onde o primeiro item é o comando a ser executado (`ls`, nesse caso) e os itens subsequentes são os argumentos.

Com essa modificação, o código se torna:

import subprocess

# subprocess.run('ls')  # Comando simples

# subprocess.run('ls -la', shell=True) # Comando perigoso

subprocess.run(['ls', '-la'])

Para capturar a saída padrão de um subprocesso em uma variável, use o argumento `capture_output=True`:

list_of_files = subprocess.run(['ls', '-la'], capture_output=True)

print(list_of_files.stdout)

❯ python list_dir.py 
b'total 36ndrwxr-xr-x 3 daniel daniel 4096 may 20 21:08 .ndrwx------ 30 daniel daniel 4096 may 20 18:03 ..n-rw-r--r-- 1 daniel daniel 55 may 20 20:18 example.pyndrwxr-xr-x 8 daniel daniel 4096 may 20 17:31 .gitn-rw-r--r-- 1 daniel daniel 2160 may 17 22:23 .gitignoren-rw-r--r-- 1 daniel daniel 271 may 20 19:53 internet_checker.pyn-rw-r--r-- 1 daniel daniel 1076 may 17 22:23 LICENSEn-rw-r--r-- 1 daniel daniel 216 may 20 22:12 list_dir.pyn-rw-r--r-- 1 daniel daniel 22 may 17 22:23 README.mdn'

O atributo `stdout` da instância permite acessar a saída do processo.

Se você deseja armazenar a saída como uma string em vez de bytes, use `text=True`:

list_of_files = subprocess.run(['ls', '-la'], capture_output=True, text=True)

print(list_of_files.stdout)

❯ python list_dir.py
total 36
drwxr-xr-x  3 daniel daniel 4096 may 20 21:08 .
drwx------ 30 daniel daniel 4096 may 20 18:03 ..
-rw-r--r--  1 daniel daniel   55 may 20 20:18 example.py
drwxr-xr-x  8 daniel daniel 4096 may 20 17:31 .git
-rw-r--r--  1 daniel daniel 2160 may 17 22:23 .gitignore
-rw-r--r--  1 daniel daniel  271 may 20 19:53 internet_checker.py
-rw-r--r--  1 daniel daniel 1076 may 17 22:23 LICENSE
-rw-r--r--  1 daniel daniel  227 may 20 22:14 list_dir.py
-rw-r--r--  1 daniel daniel   22 may 17 22:23 README.md

Agora que você entende os fundamentos da biblioteca subprocess, vamos explorar alguns exemplos de uso.

Casos de Uso Prático da Biblioteca subprocess

Nesta seção, vamos apresentar algumas aplicações práticas da biblioteca subprocess. Para uma visão mais completa, consulte o Repositório Github.

Verificação de Programa

Uma das principais aplicações dessa biblioteca é a realização de operações simples do sistema operacional.

Por exemplo, um script que verifica se um programa está instalado. Em Linux, podemos usar o comando `which`.

'''Verificador de programa com subprocess'''

import subprocess

program = 'git'

process = subprocess.run(['which', program], capture_output=True, text=True)

if process.returncode == 0: 
    print(f'O programa "{program}" está instalado')

    print(f'O caminho do binário é: {process.stdout}')
else:
    print(f'O programa {program} não está instalado')

    print(process.stderr)

Observação: Em UNIX, o código de retorno 0 indica sucesso na execução do comando. Um código diferente de 0 indica uma falha.

Por não utilizar `shell=True`, podemos aceitar entradas do usuário com segurança. Além disso, podemos usar expressões regulares para validar se a entrada é um programa válido.

import subprocess

import re

programs = input('Insira os programas separados por um espaço: ').split()

secure_pattern = 'qui'

for program in programs:

    if not re.match(secure_pattern, program):
        print("Desculpe, não podemos verificar este programa")

        continue

    process = subprocess.run(
        ['which', program], capture_output=True, text=True)

    if process.returncode == 0:
        print(f'O programa "{program}" está instalado')

        print(f'O caminho do binário é: {process.stdout}')
    else:
        print(f'O programa {program} não está instalado')

        print(process.stderr)

    print('n')

Neste caso, recebemos os programas do usuário e usamos uma expressão regular para garantir que a string do programa contenha apenas letras e dígitos. Verificamos a existência de cada programa em um loop `for`.

Grep Simplificado em Python

Um amigo, chamado Tom, tem uma lista de padrões em um arquivo de texto e precisa contar quantas vezes cada padrão aparece em outro arquivo grande. Ele gastava horas executando o comando `grep` para cada padrão.

Felizmente, com Python, esse problema pode ser resolvido em segundos.

import subprocess

patterns_file="patterns.txt"
readfile="romeo-full.txt"

with open(patterns_file, 'r') as f:
    for pattern in f:
        pattern = pattern.strip()

        process = subprocess.run(
            ['grep', '-c', f'{pattern}', readfile], capture_output=True, text=True)

        if int(process.stdout) == 0:
            print(
                f'O padrão "{pattern}" não foi encontrado em {readfile}')

            continue

        print(f'O padrão "{pattern}" foi encontrado {process.stdout.strip()} vezes')

Neste código, definimos duas variáveis, `patterns_file` e `readfile`, que representam os arquivos com os quais vamos trabalhar. Depois, abrimos o arquivo de padrões e iteramos sobre eles. Em seguida, executamos o comando `grep` com a flag `-c` (para contar) e exibimos o resultado da contagem com condicionais.

Se você executar este script (lembre-se de que os arquivos podem ser baixados do repositório Github)

Configurando um Ambiente Virtual (Virtualenv) com subprocess

Uma das aplicações mais interessantes do Python é a automação de processos, que pode economizar horas de trabalho.

Vamos criar um script que configura um ambiente virtual e procura por um arquivo `requirements.txt` no diretório atual, instalando as dependências.

import subprocess

from pathlib import Path


VENV_NAME = '.venv'
REQUIREMENTS = 'requirements.txt'

process1 = subprocess.run(['which', 'python3'], capture_output=True, text=True)

if process1.returncode != 0:
    raise OSError('Desculpe, python3 não está instalado')

python_bin = process1.stdout.strip()

print(f'Python encontrado em: {python_bin}')

process2 = subprocess.run('echo "$SHELL"', shell=True, capture_output=True, text=True)

shell_bin = process2.stdout.split('/')[-1]

create_venv = subprocess.run([python_bin, '-m', 'venv', VENV_NAME], check=True)

if create_venv.returncode == 0:
    print(f'Seu venv {VENV_NAME} foi criado')

pip_bin = f'{VENV_NAME}/bin/pip3'

if Path(REQUIREMENTS).exists():
    print(f'Arquivo de requisitos "{REQUIREMENTS}" encontrado')
    print('Instalando requisitos')
    subprocess.run([pip_bin, 'install', '-r', REQUIREMENTS])

    print('Processo concluído! Agora ative seu ambiente com "source .venv/bin/activate"')

else:
    print("Nenhum requisito especificado...")

Neste exemplo, executamos diversos processos e utilizamos os dados coletados no script Python. Também usamos a biblioteca pathlib para verificar a existência do arquivo `requirements.txt`.

Ao executar o script, mensagens informativas sobre o que está ocorrendo no sistema operacional serão exibidas.

❯ python setup.py 
Python encontrado em: /usr/bin/python3
Seu venv .venv foi criado
Arquivo de requisitos "requirements.txt" encontrado
Instalando requisitos
Collecting asgiref==3.3.4 .......
Processo concluído! Agora ative seu ambiente com "source .venv/bin/activate"

A saída do processo de instalação é exibida, pois não estamos redirecionando a saída padrão para uma variável.

Executando Outras Linguagens de Programação

Podemos executar programas em outras linguagens de programação com Python e obter a saída desses programas, pois os subprocessos interagem diretamente com o sistema operacional.

Para ilustrar, vamos criar um programa “Olá, Mundo!” em C++ e Java. Para executar o código, você precisará ter os compiladores C++ e Java instalados.

`olamundo.cpp`:

#include <iostream>

int main(){
    std::cout << "Este é um Olá Mundo em C++" << std::endl;
    return 0;
}

`olámundo.java`:

class HelloWorld{  
    public static void main(String args[]){  
     System.out.println("Este é um Olá Mundo em Java");  
    }  
}  

O código em C++ e Java é um pouco mais extenso do que em Python, mas é para fins de demonstração.

Vamos criar um script Python que execute todos os arquivos C++ e Java em um diretório. Para isso, vamos utilizar a biblioteca glob, que facilita a localização de arquivos por extensão.

from glob import glob

# Obtém arquivos com cada extensão
java_files = glob('*.java')

cpp_files = glob('*.cpp')

Agora, vamos usar subprocessos para executar cada tipo de arquivo:

for file in cpp_files:
    process = subprocess.run(f'g++ {file} -o out; ./out', shell=True, capture_output=True, text=True)
    
    output = process.stdout.strip() + ' BTW este foi executado por Python'

    print(output)

for file in java_files:
    without_ext = file.strip('.java')
    process = subprocess.run(f'java {file}; java {without_ext}',shell=True, capture_output=True, text=True)

    output = process.stdout.strip() + ' Um subprocesso Python executou isso :)'
    print(output)

Utilizamos a função `strip` para modificar a saída e obter apenas o que precisamos.

Atenção ao executar arquivos Java ou C++ muito grandes, pois a saída desses arquivos é carregada na memória, o que pode causar vazamentos de memória.

Abrindo Programas Externos

Podemos executar outros programas chamando o caminho de seus executáveis via subprocessos.

Vamos testar abrindo o Brave, um navegador popular:

import subprocess

subprocess.run('brave')

Isso abrirá uma nova instância do navegador ou outra aba, caso o navegador já esteja em execução.

Como em qualquer outro programa que aceita flags, podemos usá-las para ajustar o comportamento:

import subprocess

subprocess.run(['brave', '--incognito'])

Conclusão

Um subprocesso é um processo criado por outro processo. Podemos monitorar os processos do computador com ferramentas como o htop e o gerenciador de tarefas.

Python possui uma biblioteca própria para trabalhar com subprocessos. Atualmente, a função `run` oferece uma interface simples para criar e controlar subprocessos.

Podemos desenvolver diversos tipos de aplicações utilizando subprocessos, pois eles permitem interagir diretamente com o sistema operacional.

Por fim, lembre-se de que a melhor maneira de aprender é criar algo que você queira usar.