Shell script ou Python: como decidir na automação
Cenário
Toda equipe de infraestrutura chega nesse cruzamento: precisa automatizar uma tarefa — rotacionar logs, orquestrar um deploy, validar configuração antes de aplicar um manifesto — e alguém pergunta "isso a gente escreve em shell ou em Python?". A resposta correta quase nunca é "sempre X". Depende da complexidade da lógica, de quem vai manter o script depois e de que tipo de falha você não pode tolerar.
Este post não compara benchmarks de performance nem relata incidentes reais — não há fontes de pesquisa disponíveis para este tema, então o que segue é uma análise baseada em critérios técnicos e na documentação oficial de cada ferramenta, não em dados coletados ou experiência relatada em produção. Trate as recomendações como ponto de partida para decisão, não como veredito definitivo.
Premissas
- Os exemplos de código abaixo não foram executados neste ambiente; revise a sintaxe antes de usar em produção.
- O post assume Bash como shell de referência (presente por padrão na maioria das distribuições Linux) e Python 3.x como alternativa.
- Quando a tarefa envolve orquestração multi-host complexa, ferramentas de IaC/configuration management (Ansible, Terraform, Salt) competem com as duas opções abaixo — isso é assunto para outro post, aqui o foco é script único, não uma plataforma de automação completa.
Critérios de decisão
Antes de escolher a linguagem, responda três perguntas:
- A tarefa é majoritariamente orquestração de comandos existentes ou lógica de processamento de dados? Encadear
tar,rsync,systemctlecurlé o terreno natural do shell. Parsear JSON, validar schemas, fazer retry com backoff exponencial ou manipular estruturas de dados complexas pesa para Python. - Quem vai dar manutenção nisso em seis meses? Um script shell de 200 linhas com muitos
ifaninhados e parsing de texto frágil é difícil de depurar para quem não escreveu. Python com funções nomeadas, testes e type hints reduz essa fricção. - Qual o custo de um erro silencioso? Shell script tem comportamento de erro traiçoeiro por padrão — um comando que falha no meio de um pipeline pode não interromper a execução se você não configurar isso explicitamente.
Diferenças práticas
Tratamento de erro
Em Bash, erro não interrompe a execução a menos que você configure isso. A prática recomendada é abrir o script com:
#!/usr/bin/env bash
set -euo pipefail
set -e: encerra o script se qualquer comando retornar código de saída diferente de zero.set -u: trata variável não definida como erro.set -o pipefail: propaga falha de qualquer estágio de um pipe, não só do último comando.
Essas opções estão documentadas no manual oficial do Bash (gnu.org/software/bash/manual), na seção "The Set Builtin". Mesmo com elas, shell script ainda sofre com falhas de word splitting e expansão de glob se você não citar variáveis corretamente — é um erro comum mesmo em scripts "maduros".
Em Python, exceções são o modelo padrão: um subprocess.run(..., check=True) levanta CalledProcessError se o comando falhar, e você decide explicitamente onde capturar isso:
import subprocess
try:
subprocess.run(["systemctl", "restart", "nginx"], check=True)
except subprocess.CalledProcessError as exc:
print(f"Falha ao reiniciar nginx: {exc}")
raise
O comportamento de subprocess.run e o parâmetro check estão documentados em docs.python.org/3/library/subprocess.html.
Manipulação de dados estruturados
Parsing de JSON ou YAML em shell depende de ferramentas externas (jq, yq) e o resultado ainda é texto — qualquer lógica condicional sobre esse dado volta a ser manipulação de string. Em Python, você carrega o JSON como dicionário nativo:
import json
with open("config.json") as f:
config = json.load(f)
if config.get("replicas", 1) < 2:
print("Aviso: replicas insuficientes para alta disponibilidade")
Para configurações de infraestrutura (que cada vez mais são JSON ou YAML), essa diferença pesa bastante a favor de Python conforme a lógica de validação cresce.
Portabilidade e dependências
Shell script roda em qualquer ambiente com Bash (ou até sh POSIX, se você evitar bashismos) sem instalar nada — isso importa em imagens de container minimalistas ou ambientes air-gapped. Python exige o interpretador presente e, se o script usar bibliotecas de terceiros, um ambiente virtual ou gerenciamento de dependências (venv, pip, poetry). Isso é custo operacional real: mais uma coisa para versionar e manter atualizada por questões de segurança.
Legibilidade e manutenção em equipe
Scripts shell curtos (até ~50 linhas, lógica linear) são lidos rapidamente por qualquer pessoa da equipe. Passado esse tamanho, funções, escopo de variável e debugging ficam mais difíceis — não existe um equivalente direto a um debugger interativo robusto como o pdb do Python, e testes automatizados de shell script (com bats, por exemplo) são menos comuns na prática do que testes unitários em Python com pytest.
Quando usar shell script
- Tarefa é essencialmente orquestrar comandos de sistema já existentes (systemd, pacotes, arquivos).
- Script roda em ambiente com superfície mínima de dependências (imagem base de container, bootstrap de VM).
- Lógica é linear, sem necessidade de estruturas de dados complexas.
- Vai ser chamado dentro de um pipeline CI/CD como um passo isolado e simples.
Quando usar Python
- Lógica envolve parsing, validação ou transformação de dados estruturados (JSON, YAML, respostas de API).
- Você precisa de tratamento de erro granular, retries com backoff, ou chamadas a APIs HTTP.
- O script vai crescer, ganhar testes automatizados e ser mantido por várias pessoas.
- Você já depende de bibliotecas específicas (SDKs de cloud,
boto3, clientes Kubernetes) que tornam Python o caminho natural.
Abordagem híbrida
Na prática, muitos pipelines de automação combinam os dois: um script shell curto cuida do bootstrap (verificar dependências, exportar variáveis de ambiente, invocar o passo principal) e delega a lógica pesada para um script Python. Isso evita reescrever em Python tarefas triviais de orquestração e evita forçar shell a fazer parsing que ele não faz bem.
#!/usr/bin/env bash
set -euo pipefail
if ! command -v python3 &>/dev/null; then
echo "python3 não encontrado" >&2
exit 1
fi
python3 ./valida_config.py "$1"
Esse padrão também facilita a migração gradual: você troca partes de um script shell legado por módulos Python conforme a complexidade justifica, sem precisar reescrever tudo de uma vez.
Recomendação condicionada
Se a tarefa é orquestração simples e linear, com dependências mínimas e vida curta, shell script com set -euo pipefail e shellcheck no CI resolve com menos atrito. Se a lógica envolve dados estruturados, tratamento de erro detalhado ou vai crescer além de uma tela de código, comece em Python — o custo inicial de setup (ambiente, dependências) se paga em manutenibilidade.
Essa escolha muda se a equipe já tem um padrão estabelecido: forçar Python numa equipe que só mantém shell (ou vice-versa) troca um problema técnico por um problema de adoção, que costuma ser mais caro. Avalie o script existente antes de decidir a linguagem do próximo.
Como próximo passo prático, pegue um script de automação que você mantém hoje e aplique o checklist das três perguntas da seção de critérios — se a resposta apontar para a linguagem errada, é sinal de que vale planejar a migração antes que o script cresça mais.
Fontes
- Bash Reference Manual — The Set Builtin: gnu.org/software/bash/manual/bash.html
- Python 3 Documentation — módulo
subprocess: docs.python.org/3/library/subprocess.html