O shell é uma ferramenta poderosa para automação e orquestração de tarefas, mas nem sempre é a melhor escolha para processamento complexo de dados, manipulação de arquivos binários, ou implementação de algoritmos avançados. Integrar o shell com outras linguagens, como Python, permite combinar a simplicidade de scripts de shell com a expressividade e bibliotecas de linguagens de propósito geral. Nesta aula, vamos explorar como fazer essa integração de forma eficiente, passando dados entre o shell e outras linguagens, e discutir quando vale a pena migrar parte do código para uma linguagem mais robusta.

Vamos começar com a forma mais simples: chamar scripts Python a partir do shell. Em seguida, veremos como passar argumentos, usar saída de programas, e até mesmo comunicação bidirecional. Ao final, você terá um repertório de técnicas para decidir quando usar shell puro e quando integrar outra linguagem.

Chamando Python/scripts

A forma mais comum de integrar shell com Python é simplesmente invocar o interpretador Python dentro de um script de shell. Isso pode ser feito de várias maneiras, dependendo da necessidade: chamar um script Python existente, executar código Python inline, ou até mesmo usar shebang para tornar o script Python executável diretamente.

Para chamar um script Python de dentro de um script shell, basta usar o comando python3 (ou python dependendo do sistema) seguido do caminho do script. Por exemplo, se você tem um arquivo process.py que recebe argumentos, você pode chamá-lo assim:

#!/bin/bash
# script.sh
python3 process.py "arg1" "arg2"

Se você quiser executar um trecho de código Python diretamente sem criar um arquivo separado, pode usar a opção -c do interpretador:

python3 -c "print('Olá do Python')"

Outra abordagem é usar python3 - para ler o código da entrada padrão, o que permite passar código via pipe:

echo "print('Hello from pipe')" | python3 -

Além disso, é possível tornar um script Python executável diretamente, adicionando um shebang no início do arquivo Python e marcando-o como executável. Assim, você pode chamar o script diretamente pelo nome, como se fosse um comando do shell:

#!/usr/bin/env python3
# process.py
import sys
print('Argumentos:', sys.argv[1:])
chmod +x process.py
./process.py "arg1" "arg2"

Essa abordagem é útil para scripts que são essencialmente Python, mas que precisam ser integrados em pipelines de shell. Lembre-se de que o shebang deve apontar para o interpretador correto (no Linux, geralmente /usr/bin/env python3 é mais portátil).

Passando dados

Passar dados entre o shell e outras linguagens pode ser feito de várias formas: argumentos de linha de comando, entrada padrão, variáveis de ambiente, arquivos temporários, e até mesmo pipes bidirecionais. A escolha depende da quantidade de dados e da natureza da comunicação.

Argumentos de linha de comando são ideais para pequenas quantidades de dados, como parâmetros de configuração. No shell, você pode passar variáveis como argumentos:

nome="Maria"
idade=30
python3 saudacao.py "$nome" "$idade"

No Python, esses argumentos são acessíveis via sys.argv.

Entrada padrão é útil para dados maiores, como conteúdo de arquivos ou saída de outros comandos. Você pode usar um pipe para enviar dados para o script Python:

cat dados.txt | python3 processa_dados.py

Ou, no script Python, ler de sys.stdin:

import sys
dados = sys.stdin.read()
print('Recebido:', len(dados), 'caracteres')

Variáveis de ambiente são outra forma de passar dados, especialmente para configurações. No shell, você define a variável e a exporta:

export MODO="producao"
python3 script.py

No Python, você acessa via os.environ.

Arquivos temporários são adequados para dados estruturados (como JSON ou CSV) que precisam ser persistidos entre execuções. O shell pode criar um arquivo com a saída de um comando e depois passar o caminho para o Python:

comando_que_gera_dados > /tmp/dados.json
python3 processa.py /tmp/dados.json

Finalmente, pipes bidirecionais permitem comunicação contínua entre o shell e o Python, mas são mais complexos. Você pode usar coproc no Bash ou ferramentas como expect. Para a maioria dos casos, os métodos anteriores são suficientes.

Um exemplo prático de passagem de dados via pipe e argumentos:

#!/bin/bash
# Soma números passados por uma lista
numeros="1 2 3 4 5"
echo "$numeros" | python3 -c "
import sys
nums = [int(x) for x in sys.stdin.read().split()]
print('Soma:', sum(nums))
"

Quando migrar de linguagem

Saber quando migrar de shell para uma linguagem como Python é crucial para manter a produtividade e a manutenibilidade do código. O shell é excelente para orquestrar comandos, manipular arquivos de texto, e automação de tarefas simples. No entanto, ele se torna frágil quando a lógica fica complexa, quando há necessidade de estruturas de dados avançadas, ou quando o desempenho é crítico.

Aqui estão alguns sinais de que você deve considerar migrar parte do código para Python ou outra linguagem:

  • Lógica condicional complexa: se você está aninhando muitos if e case no shell, Python oferece estruturas mais claras.
  • Manipulação de dados estruturados: JSON, CSV, XML, etc. Python tem bibliotecas robustas (como json, csv, xml.etree) que simplificam o processamento.
  • Expressões regulares avançadas: embora o shell tenha grep e sed, Python fornece o módulo re com mais controle e legibilidade.
  • Operações matemáticas ou científicas: Python com NumPy/SciPy é muito mais adequado.
  • Desenvolvimento de aplicações maiores: se o script está crescendo além de algumas centenas de linhas, considere uma linguagem mais estruturada.
  • Testabilidade e manutenção: Python permite escrever testes unitários facilmente, enquanto testar shell é mais difícil.

No entanto, não é necessário migrar tudo de uma vez. Uma estratégia comum é manter o shell para orquestração e chamar Python para tarefas específicas que exigem mais poder. Por exemplo, um script de backup pode usar shell para montar diretórios e chamar Python para compactar e criptografar.

Um exemplo de quando migrar: se você precisa processar um arquivo CSV grande com validações e transformações, o shell pode se tornar lento e difícil de manter. Em Python, você pode usar a biblioteca csv e pandas para fazer isso de forma eficiente.

Exemplos

Vamos consolidar o que aprendemos com alguns exemplos práticos de integração shell+Python.

Exemplo 1: Chamar um script Python para processar dados de um arquivo

Suponha que você tenha um arquivo vendas.csv com colunas produto,quantidade,preco. Você quer calcular o total de vendas. Em vez de fazer isso com awk (que seria possível, mas menos legível), você pode usar Python:

#!/bin/bash
# total_vendas.sh
python3 - <

Note o uso de <<EOF (here-document) para passar o código Python diretamente no script shell. Isso é útil para scripts pequenos.

Exemplo 2: Usar a saída de um comando Python em uma variável do shell

#!/bin/bash
# capturar_saida.sh
resultado=$(python3 -c "print('Olá mundo')")
echo "O Python disse: $resultado"

Isso usa substituição de comando para capturar a saída padrão do Python em uma variável.

Exemplo 3: Passar argumentos e receber saída

#!/bin/bash
# soma.py
import sys
nums = [int(x) for x in sys.argv[1:]]
print(sum(nums))
#!/bin/bash
# chama_soma.sh
resultado=$(python3 soma.py 10 20 30)
echo "A soma é: $resultado"

Esse padrão é útil para funções matemáticas ou processamento que produzem um resultado único.

Exemplo 4: Comunicação bidirecional (avançado)

Para comunicação mais complexa, você pode usar coproc no Bash, que permite manter um processo em execução e interagir com ele. Exemplo:

#!/bin/bash
coproc python3 -i
# Envia um comando para o Python
echo "print(2+2)" >&"${COPROC[1]}"
# Lê a saída
read -r resultado <&"${COPROC[0]}"
echo "Resultado: $resultado"

Nesse exemplo, python3 -i inicia um interpretador interativo, e usamos o descritor de arquivo para enviar comandos e ler a saída. Isso é avançado e raramente necessário, mas mostra o potencial.

Boas práticas e observações finais

Ao integrar shell com outras linguagens, siga estas boas práticas para evitar problemas:

  • Use set -e no shell para que o script falhe se algum comando falhar, evitando que erros passem despercebidos.
  • Capture e trate erros do script Python verificando o código de saída ($?) e a saída de erro (stderr).
  • Prefira passar dados via entrada padrão para grandes volumes, em vez de argumentos, para evitar limites de tamanho.
  • Use here-documents com cuidado: se o código Python contém caracteres especiais como $, pode ser necessário escapar ou usar <<'EOF' para evitar expansão pelo shell.
  • Considere usar python3 -c para trechos curtos, mas para scripts mais longos, crie um arquivo separado para melhor legibilidade e manutenção.
  • Documente as interfaces entre shell e Python (argumentos, formatos de dados) para facilitar a colaboração.

Em resumo, integrar shell com outras linguagens é uma habilidade valiosa para qualquer desenvolvedor de sistemas. O shell continua sendo a ferramenta de orquestração por excelência, mas ao combinar com Python, você ganha expressividade e robustez. Use as técnicas apresentadas para criar scripts poderosos e manuteníveis.

Referências

Exercícios

  1. Exercício 1: Escreva um script shell que chame um script Python para calcular o fatorial de um número passado como argumento. O script Python deve estar em um arquivo separado fatorial.py e deve receber o número via argumento de linha de comando.

    ✓ Resposta:

    fatorial.py:

    import sys
    
    def fatorial(n):
        if n < 0:
            return None
        if n == 0:
            return 1
        return n * fatorial(n-1)
    
    if __name__ == "__main__":
        if len(sys.argv) != 2:
            print("Uso: python3 fatorial.py N", file=sys.stderr)
            sys.exit(1)
        n = int(sys.argv[1])
        print(fatorial(n))
    

    script.sh:

    #!/bin/bash
    if [ $# -ne 1 ]; then
        echo "Uso: $0 N"
        exit 1
    fi
    resultado=$(python3 fatorial.py "$1")
    if [ $? -eq 0 ]; then
        echo "Fatorial de $1 é $resultado"
    else
        echo "Erro ao calcular fatorial"
    fi
    
  2. Exercício 2: Crie um script shell que use um pipe para enviar o conteúdo de um arquivo de texto para um script Python que conta o número de palavras. O script Python deve ler da entrada padrão.

    ✓ Resposta:
    #!/bin/bash
    # conta_palavras.sh
    if [ $# -lt 1 ]; then
        echo "Uso: $0 arquivo"
        exit 1
    fi
    cat "$1" | python3 -c "
    import sys
    texto = sys.stdin.read()
    palavras = texto.split()
    print('Número de palavras:', len(palavras))
    "
    
  3. Exercício 3: Escreva um script shell que defina uma variável de ambiente e a passe para um script Python, que a imprime. Use export no shell e os.environ no Python.

    ✓ Resposta:
    #!/bin/bash
    # script_env.sh
    export MINHA_VAR="Olá do shell"
    python3 -c "
    import os
    print('Variável recebida:', os.environ.get('MINHA_VAR'))
    "
    
  4. Exercício 4: Crie um script shell que capture a saída de um comando Python (por exemplo, a data atual formatada) em uma variável e a use em uma mensagem. Use substituição de comando.

    ✓ Resposta:
    #!/bin/bash
    # data_python.sh
    data=$(python3 -c "from datetime import datetime; print(datetime.now().strftime('%d/%m/%Y %H:%M'))")
    echo "Data e hora atual: $data"
    
  5. Exercício 5: Escreva um script shell que use um here-document para passar um código Python que lê um arquivo CSV e imprime a média de uma coluna. O script shell deve aceitar o nome do arquivo como argumento.

    ✓ Resposta:
    #!/bin/bash
    # media_csv.sh
    if [ $# -lt 2 ]; then
        echo "Uso: $0 arquivo.csv coluna"
        exit 1
    fi
    python3 - "$1" "$2" <<EOF
    import csv
    import sys
    
    arquivo = sys.argv[1]
    coluna = sys.argv[2]
    total = 0
    count = 0
    
    with open(arquivo, newline='') as f:
        reader = csv.DictReader(f)
        for row in reader:
            if coluna in row:
                try:
                    total += float(row[coluna])
                    count += 1
                except ValueError:
                    pass
    
    if count > 0:
        print(f'Média da coluna {coluna}: {total/count:.2f}')
    else:
        print('Nenhum dado válido encontrado.')
    EOF