Python

Guia Completo de Automação de Tarefas em Python: subprocess, shutil e Scripting Real

17 min de leitura

Guia Completo de Automação de Tarefas em Python: subprocess, shutil e Scripting Real

Entendendo Automação de Tarefas em Python Automação de tarefas é um dos pilares da programação moderna. Como profissional, posso garantir que 70% do meu trabalho envolve eliminar tarefas repetitivas através de scripts bem estruturados. Python se destaca nessa área porque possui bibliotecas robustas que permitem interagir diretamente com o sistema operacional, executar programas externos e manipular arquivos de forma elegante. Neste artigo, vamos explorar três ferramentas essenciais: o módulo para execução de comandos do sistema, para manipulação de arquivos, e técnicas práticas de scripting que você usará em projetos reais. O objetivo não é apenas mostrar sintaxe, mas desenvolver a mentalidade de um profissional que escreve automações confiáveis e mantíveis. Subprocess: Executando Comandos do Sistema Conceitos Fundamentais O módulo permite que seu código Python execute comandos do terminal/prompt como se você estivesse digitando manualmente. A diferença crucial em relação aos módulos antigos ( ) é que você tem controle total sobre entrada, saída e códigos de erro. Isso significa capturar

<h2>Entendendo Automação de Tarefas em Python</h2>

<p>Automação de tarefas é um dos pilares da programação moderna. Como profissional, posso garantir que 70% do meu trabalho envolve eliminar tarefas repetitivas através de scripts bem estruturados. Python se destaca nessa área porque possui bibliotecas robustas que permitem interagir diretamente com o sistema operacional, executar programas externos e manipular arquivos de forma elegante.</p>

<p>Neste artigo, vamos explorar três ferramentas essenciais: o módulo <code>subprocess</code> para execução de comandos do sistema, <code>shutil</code> para manipulação de arquivos, e técnicas práticas de scripting que você usará em projetos reais. O objetivo não é apenas mostrar sintaxe, mas desenvolver a mentalidade de um profissional que escreve automações confiáveis e mantíveis.</p>

<h2>Subprocess: Executando Comandos do Sistema</h2>

<h3>Conceitos Fundamentais</h3>

<p>O módulo <code>subprocess</code> permite que seu código Python execute comandos do terminal/prompt como se você estivesse digitando manualmente. A diferença crucial em relação aos módulos antigos (<code>os.system()</code>) é que você tem controle total sobre entrada, saída e códigos de erro. Isso significa capturar resultados, passar argumentos com segurança e tratar exceções adequadamente.</p>

<p>Existem dois cenários principais: você quer apenas executar um comando (sem capturar saída) ou precisa do resultado para processar depois. Para a maioria dos casos modernos, você usará <code>subprocess.run()</code> ou <code>subprocess.Popen()</code>.</p>

<h3>Usando subprocess.run() para Tarefas Simples</h3>

<p>O <code>subprocess.run()</code> é a escolha padrão para comandos de execução única. Ele aguarda o término do processo e retorna um objeto com informações sobre a execução.</p>

<pre><code class="language-python">import subprocess

Exemplo 1: Executar um comando simples

resultado = subprocess.run([&#039;ls&#039;, &#039;-la&#039;], capture_output=True, text=True)

print(&quot;Saída:&quot;, resultado.stdout)

print(&quot;Código de retorno:&quot;, resultado.returncode)

Exemplo 2: Tratando erros com check=True

try:

subprocess.run([&#039;mkdir&#039;, &#039;/tmp/meu_diretorio&#039;], check=True)

print(&quot;Diretório criado com sucesso&quot;)

except subprocess.CalledProcessError as e:

print(f&quot;Erro ao criar diretório: {e}&quot;)

Exemplo 3: Passando entrada para o comando

resultado = subprocess.run(

[&#039;grep&#039;, &#039;erro&#039;],

input=&#039;linha com erro\nlinha normal\n&#039;,

capture_output=True,

text=True

)

print(&quot;Resultados encontrados:&quot;, resultado.stdout)</code></pre>

<p>Note que <code>capture_output=True</code> redireciona stdout e stderr, enquanto <code>text=True</code> converte bytes para string (python 3.7+). O <code>check=True</code> levanta uma exceção se o comando retornar código não-zero, o que é essencial para detecção de erros em scripts de automação.</p>

<h3>Popen para Processos Contínuos</h3>

<p>Quando você precisa interagir continuamente com um processo, ou precisa de mais controle granular, use <code>Popen</code>. Este é um cenário menos comum, mas fundamental em automações avançadas.</p>

<pre><code class="language-python">import subprocess

import time

Executar um processo em background e monitorar

processo = subprocess.Popen(

[&#039;ping&#039;, &#039;-c&#039;, &#039;4&#039;, &#039;google.com&#039;],

stdout=subprocess.PIPE,

stderr=subprocess.PIPE,

text=True

)

Ler output em tempo real

for linha in processo.stdout:

print(f&quot;Ping: {linha.strip()}&quot;)

codigo_retorno = processo.wait()

print(f&quot;Processo finalizado com código: {codigo_retorno}&quot;)

Exemplo 2: Timeout - importante para não travar

try:

resultado = subprocess.run(

[&#039;sleep&#039;, &#039;10&#039;],

timeout=2 # Vai cancelar após 2 segundos

)

except subprocess.TimeoutExpired:

print(&quot;Processo excedeu o timeout&quot;)</code></pre>

<p>A lição crucial aqui: sempre use <code>timeout</code> em automações críticas. Processos que travarem podem derrubar sua automação inteira. Em produção, isso causa downtime.</p>

<h2>Manipulação de Arquivos com Shutil</h2>

<h3>Por que não usar os.rename() e os.remove()?</h3>

<p>O módulo <code>shutil</code> foi criado para operações de alto nível com arquivos. Enquanto <code>os.remove()</code> remove apenas um arquivo e <code>os.rename()</code> funciona apenas em casos simples, <code>shutil</code> oferece funções que funcionam consistentemente entre Windows e Linux, tratam permissões e funcionam com diretórios inteiros. Em automação real, essa compatibilidade é ouro.</p>

<h3>Operações Essenciais: Copy, Move e Remove</h3>

<pre><code class="language-python">import shutil

import os

Exemplo 1: Copiar arquivo mantendo metadados

origem = &#039;/tmp/arquivo_original.txt&#039;

destino = &#039;/tmp/backup/arquivo_original.txt&#039;

copy2 preserva timestamps e permissões (melhor que copy)

shutil.copy2(origem, destino)

print(&quot;Arquivo copiado com sucesso&quot;)

Exemplo 2: Copiar árvore de diretórios inteira

shutil.copytree(

&#039;/tmp/projeto_antigo&#039;,

&#039;/tmp/projeto_backup&#039;,

dirs_exist_ok=True # Não falha se diretório existe

)

Exemplo 3: Mover arquivo (renomear com segurança)

shutil.move(&#039;/tmp/arquivo.txt&#039;, &#039;/home/usuario/arquivo.txt&#039;)

Exemplo 4: Remover árvore de diretórios (CUIDADO!)

Esta operação é irreversível

if os.path.exists(&#039;/tmp/pasta_temporaria&#039;):

shutil.rmtree(&#039;/tmp/pasta_temporaria&#039;)

print(&quot;Pasta removida&quot;)

Exemplo 5: Obter tamanho de um diretório

tamanho = shutil.disk_usage(&#039;/home/usuario&#039;)

print(f&quot;Total: {tamanho.total / (1024**3):.2f} GB&quot;)

print(f&quot;Livre: {tamanho.free / (1024**3):.2f} GB&quot;)</code></pre>

<p>A grande vantagem do <code>shutil.copytree()</code> com <code>dirs_exist_ok=True</code> é que você pode rodar backups incrementais sem se preocupar com tratamento de exceções. Em comparação com <code>os.rename()</code>, o <code>shutil.move()</code> funciona até entre sistemas de arquivos diferentes (por exemplo, de <code>/tmp</code> para <code>/home</code> em Linux), algo que <code>os.rename()</code> não garante.</p>

<h3>Compactação de Arquivos</h3>

<p>Muitas automações reais precisam comprimir arquivos para backup ou transferência. O <code>shutil</code> integra isso elegantemente.</p>

<pre><code class="language-python">import shutil

Exemplo 1: Criar arquivo tar.gz

shutil.make_archive(

base_name=&#039;/tmp/backup_projeto&#039;, # Sem extensão

format=&#039;gztar&#039;, # Cria .tar.gz

root_dir=&#039;/home/usuario/projeto&#039;

)

Exemplo 2: Extrair arquivo

shutil.unpack_archive(

&#039;/tmp/backup_projeto.tar.gz&#039;,

extract_dir=&#039;/tmp/restaurado&#039;

)

Exemplo 3: Listar formatos disponíveis

print(shutil.get_archive_formats())</code></pre>

<h2>Scripting Real: Integrando Tudo</h2>

<h3>Estrutura Profissional de um Script</h3>

<p>Um script de automação profissional segue padrões claros. Não é apenas uma sequência de comandos; é código que pode falhar, ser debugado e mantido por outros. Vamos construir um script real de backup com logging, validações e tratamento de erro adequado.</p>

<pre><code class="language-python">#!/usr/bin/env python3

&quot;&quot;&quot;

Script de Backup Automatizado

Realiza backup de diretórios, compacta e limpa antigos

&quot;&quot;&quot;

import subprocess

import shutil

import os

import logging

from datetime import datetime, timedelta

Configuração de logging

logging.basicConfig(

level=logging.INFO,

format=&#039;%(asctime)s - %(levelname)s - %(message)s&#039;,

handlers=[

logging.FileHandler(&#039;/var/log/backup.log&#039;),

logging.StreamHandler() # Também imprime no console

]

)

logger = logging.getLogger(__name__)

class BackupAutomacao:

def __init__(self, origem, destino, retencao_dias=30):

self.origem = origem

self.destino = destino

self.retencao_dias = retencao_dias

def validar_origem(self):

&quot;&quot;&quot;Verifica se origem existe&quot;&quot;&quot;

if not os.path.exists(self.origem):

logger.error(f&quot;Origem não existe: {self.origem}&quot;)

raise FileNotFoundError(f&quot;Origem inválida: {self.origem}&quot;)

logger.info(f&quot;Origem validada: {self.origem}&quot;)

def criar_destino(self):

&quot;&quot;&quot;Cria diretório de destino se não existir&quot;&quot;&quot;

os.makedirs(self.destino, exist_ok=True)

logger.info(f&quot;Diretório de destino pronto: {self.destino}&quot;)

def executar_backup(self):

&quot;&quot;&quot;Realiza o backup compactado&quot;&quot;&quot;

timestamp = datetime.now().strftime(&#039;%Y%m%d_%H%M%S&#039;)

nome_arquivo = f&quot;backup_{timestamp}&quot;

caminho_completo = os.path.join(self.destino, nome_arquivo)

try:

logger.info(f&quot;Iniciando backup: {self.origem}&quot;)

shutil.make_archive(

base_name=caminho_completo,

format=&#039;gztar&#039;,

root_dir=self.origem

)

tamanho = os.path.getsize(f&quot;{caminho_completo}.tar.gz&quot;) / (1024**2)

logger.info(f&quot;Backup concluído: {caminho_completo}.tar.gz ({tamanho:.2f} MB)&quot;)

return f&quot;{caminho_completo}.tar.gz&quot;

except Exception as e:

logger.error(f&quot;Erro ao executar backup: {e}&quot;)

raise

def limpar_antigos(self):

&quot;&quot;&quot;Remove backups mais antigos que retencao_dias&quot;&quot;&quot;

limite = datetime.now() - timedelta(days=self.retencao_dias)

for arquivo in os.listdir(self.destino):

caminho = os.path.join(self.destino, arquivo)

Obter tempo de modificação

tempo_mod = datetime.fromtimestamp(os.path.getmtime(caminho))

if tempo_mod &lt; limite and arquivo.endswith(&#039;.tar.gz&#039;):

try:

os.remove(caminho)

logger.info(f&quot;Backup antigo removido: {arquivo}&quot;)

except Exception as e:

logger.warning(f&quot;Não foi possível remover {arquivo}: {e}&quot;)

def executar(self):

&quot;&quot;&quot;Executa pipeline completo&quot;&quot;&quot;

try:

self.validar_origem()

self.criar_destino()

self.executar_backup()

self.limpar_antigos()

logger.info(&quot;Pipeline de backup concluído com sucesso&quot;)

return True

except Exception as e:

logger.critical(f&quot;Falha no pipeline: {e}&quot;)

return False

Uso do script

if __name__ == &#039;__main__&#039;:

backup = BackupAutomacao(

origem=&#039;/home/usuario/dados_importante&#039;,

destino=&#039;/mnt/backup/diario&#039;,

retencao_dias=30

)

sucesso = backup.executar()

exit(0 if sucesso else 1)</code></pre>

<p>Este script demonstra padrões profissionais: logging estruturado para debugging, classes para organização, validações antes de operações críticas, tratamento de exceções específicas, e um retorno de código que pode ser usado em cron jobs ou pipelines CI/CD.</p>

<h3>Script de Sincronização e Monitoramento</h3>

<p>Outro cenário comum é sincronizar diretórios e monitorar mudanças. Vamos criar um script que usa <code>subprocess</code> para chamar <code>rsync</code> (ferramenta de sincronização do sistema) e valida o resultado.</p>

<pre><code class="language-python">#!/usr/bin/env python3

&quot;&quot;&quot;

Sincronizador com Validação

Usa rsync para sincronizar e valida integridade

&quot;&quot;&quot;

import subprocess

import hashlib

import os

def sincronizar_com_rsync(origem, destino, verbose=False):

&quot;&quot;&quot;Sincroniza com rsync preservando permissões e timestamps&quot;&quot;&quot;

comando = [

&#039;rsync&#039;,

&#039;-avz&#039;, # archive, verbose, compress

&#039;--delete&#039;, # Remove arquivos no destino que não existem na origem

origem,

destino

]

try:

resultado = subprocess.run(

comando,

capture_output=True,

text=True,

check=True

)

if verbose:

print(&quot;Output rsync:&quot;, resultado.stdout)

return True, &quot;Sincronização bem-sucedida&quot;

except subprocess.CalledProcessError as e:

return False, f&quot;Erro rsync: {e.stderr}&quot;

def validar_integridade(arquivo_origem, arquivo_destino):

&quot;&quot;&quot;Compara hash SHA256 de dois arquivos&quot;&quot;&quot;

def calcular_hash(caminho):

hash_obj = hashlib.sha256()

with open(caminho, &#039;rb&#039;) as f:

for chunk in iter(lambda: f.read(4096), b&#039;&#039;):

hash_obj.update(chunk)

return hash_obj.hexdigest()

hash_origem = calcular_hash(arquivo_origem)

hash_destino = calcular_hash(arquivo_destino)

return hash_origem == hash_destino

def executar_sincronizacao(origem, destino):

&quot;&quot;&quot;Pipeline: sincronizar, validar e relatar&quot;&quot;&quot;

Sincronizar

sucesso, mensagem = sincronizar_com_rsync(origem, destino, verbose=True)

if not sucesso:

print(f&quot;FALHA: {mensagem}&quot;)

return False

print(f&quot;SUCESSO: {mensagem}&quot;)

Validar alguns arquivos críticos

arquivos_criticos = [f for f in os.listdir(destino) if f.endswith(&#039;.conf&#039;)]

if arquivos_criticos:

arquivo_teste = os.path.join(destino, arquivos_criticos[0])

arquivo_origem_teste = os.path.join(origem, arquivos_criticos[0])

if validar_integridade(arquivo_origem_teste, arquivo_teste):

print(&quot;✓ Validação de integridade OK&quot;)

return True

else:

print(&quot;✗ Falha na validação de integridade&quot;)

return False

return True

Uso

if __name__ == &#039;__main__&#039;:

resultado = executar_sincronizacao(

&#039;/home/usuario/dados/&#039;,

&#039;/backup/sincronizado/&#039;

)

exit(0 if resultado else 1)</code></pre>

<h2>Padrões Avançados e Boas Práticas</h2>

<h3>Integração com Cron e Systemd</h3>

<p>Scripts de automação real rodam em background, geralmente por agendadores do sistema. A integração adequada faz a diferença entre um script que funciona e um que falha silenciosamente.</p>

<pre><code class="language-python">#!/usr/bin/env python3

&quot;&quot;&quot;

Script preparado para rodar via cron ou systemd

&quot;&quot;&quot;

import sys

import logging

from pathlib import Path

Garantir que logging funciona mesmo com cron

log_dir = Path(&#039;/var/log/meus_scripts&#039;)

log_dir.mkdir(exist_ok=True)

logging.basicConfig(

level=logging.INFO,

format=&#039;%(asctime)s - %(name)s - %(levelname)s - %(message)s&#039;,

filename=str(log_dir / &#039;automacao.log&#039;)

)

logger = logging.getLogger(__name__)

try:

Seu código de automação aqui

logger.info(&quot;Script iniciado&quot;)

Se tudo funcionar

logger.info(&quot;Script finalizado com sucesso&quot;)

sys.exit(0)

except Exception as e:

logger.exception(f&quot;Erro crítico: {e}&quot;)

sys.exit(1)</code></pre>

<p>Para rodar via cron, adicione à crontab:</p>

<pre><code>0 2 * /usr/bin/python3 /home/usuario/automacao.py</code></pre>

<p>Para systemd, crie <code>/etc/systemd/system/automacao.service</code>:</p>

<pre><code class="language-ini">[Unit]

Description=Automação de Tarefas

After=network.target

[Service]

ExecStart=/usr/bin/python3 /home/usuario/automacao.py

User=usuario

StandardOutput=journal

StandardError=journal

[Install]

WantedBy=multi-user.target</code></pre>

<h3>Tratamento de Dependências Externas</h3>

<p>Nem sempre <code>rsync</code>, <code>git</code> ou outras ferramentas estão instaladas. Um profissional verifica isso antes.</p>

<pre><code class="language-python">import subprocess

import shutil

def verificar_comando_disponivel(comando):

&quot;&quot;&quot;Verifica se um comando existe no PATH&quot;&quot;&quot;

return shutil.which(comando) is not None

def instalar_se_necessario(comando, package_manager=&#039;apt&#039;):

&quot;&quot;&quot;Tenta instalar dependência se não existir&quot;&quot;&quot;

if verificar_comando_disponivel(comando):

return True

logger.warning(f&quot;Comando {comando} não encontrado, tentando instalar&quot;)

try:

subprocess.run(

[package_manager, &#039;install&#039;, &#039;-y&#039;, comando],

check=True,

capture_output=True

)

logger.info(f&quot;{comando} instalado com sucesso&quot;)

return True

except subprocess.CalledProcessError:

logger.error(f&quot;Falha ao instalar {comando}&quot;)

return False

No início do seu script

if not verificar_comando_disponivel(&#039;rsync&#039;):

if not instalar_se_necessario(&#039;rsync&#039;):

raise RuntimeError(&quot;rsync é necessário para este script&quot;)</code></pre>

<h2>Conclusão</h2>

<p>Você aprendeu três lições fundamentais que separam profissionais de iniciantes: <strong>Primeiro</strong>, o <code>subprocess</code> não é apenas para rodar comandos — é sobre capturar resultados, tratar erros com <code>check=True</code> e <code>timeout</code>, e entender que falhas silenciosas são piores que exceções. <strong>Segundo</strong>, o <code>shutil</code> resolve problemas reais de forma multiplataforma, desde cópias com metadados até compactação, coisas que <code>os</code> simples não garante funcionarem em todos os sistemas. <strong>Terceiro</strong>, scripting real é sobre logging estruturado, validações antes de operações irreversíveis, e code que outras pessoas conseguem manter seis meses depois.</p>

<p>A mensagem final: automação não é conveniência, é confiabilidade. Um script que falha silenciosamente é pior que nenhum script. Use logging, retorne códigos de saída, valide entradas, e sempre tenha uma estratégia de rollback para operações críticas.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://docs.python.org/3/library/subprocess.html" target="_blank" rel="noopener noreferrer">Documentação oficial subprocess - Python</a></li>

<li><a href="https://docs.python.org/3/library/shutil.html" target="_blank" rel="noopener noreferrer">Documentação oficial shutil - Python</a></li>

<li><a href="https://realpython.com/automate-tasks-with-python/" target="_blank" rel="noopener noreferrer">Real Python: Automating Tasks with Python</a></li>

<li><a href="https://www.gnu.org/software/bash/manual/" target="_blank" rel="noopener noreferrer">Linux System Administration - Scripting Best Practices</a></li>

<li><a href="https://realpython.com/python-subprocess/" target="_blank" rel="noopener noreferrer">Real Python: Python subprocess Module</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em Python

Guia Completo de Métodos Especiais em Python: __str__, __repr__, __eq__ e Dunder Methods
Guia Completo de Métodos Especiais em Python: __str__, __repr__, __eq__ e Dunder Methods

Introdução aos Dunder Methods: A Magia por Trás dos Nomes Especiais Os &quot;...

Como Usar Redis com Python: Cache, Pub/Sub e Filas com redis-py em Produção
Como Usar Redis com Python: Cache, Pub/Sub e Filas com redis-py em Produção

Redis: O Fundamento Redis é um armazenamento de dados em memória (in-memory d...

Dominando Mypy em Python: Verificação Estática de Tipos no Projeto Real em Projetos Reais
Dominando Mypy em Python: Verificação Estática de Tipos no Projeto Real em Projetos Reais

Introdução ao Mypy: Por Que Type Checking Importa Quando você trabalha em pro...