Os descritores de arquivo (FDs) são números que o kernel Linux usa para referenciar arquivos, pipes, sockets e outros recursos de I/O. Quando você executa um comando no shell, três descritores padrão são abertos: 0 (entrada padrão, geralmente o teclado), 1 (saída padrão, geralmente o terminal) e 2 (erro padrão). No entanto, o Bash permite que você manipule descritores adicionais (de 3 a 9) para controlar fluxos de dados com muito mais granularidade. Nesta aula, vamos explorar como abrir, ler e escrever em descritores de arquivo avançados, além de redirecionamentos que vão além do básico, preparando você para scripts complexos e eficientes.

Dominar descritores de arquivo avançados é uma habilidade crucial para administradores de sistemas e desenvolvedores que precisam lidar com logs, comunicação entre processos, leitura de configurações ou até mesmo sockets de rede. Vamos partir do uso do comando exec para abrir e fechar FDs, passando por redirecionamentos que combinam múltiplos fluxos, e finalizando com exemplos práticos que consolidam o conhecimento.

Abrindo FDs (exec)

O comando exec no Bash é extremamente versátil. Além de substituir o processo atual por um comando, ele pode ser usado para abrir ou fechar descritores de arquivo no shell atual. A sintaxe básica para abrir um FD é:

exec FD>arquivo   # abre para escrita (trunca)
exec FD>>arquivo  # abre para escrita (append)
exec FD<arquivo    # abre para leitura

Por exemplo, para abrir o FD 3 para escrita em um arquivo de log, você usaria exec 3>log.txt. A partir desse momento, qualquer comando que redirecione para &3 escreverá nesse arquivo. É importante notar que o FD aberto permanece disponível para todo o script até que você o feche explicitamente com exec 3>&- ou exec 3<&- (para leitura). Essa persistência é fundamental para scripts que precisam manter múltiplos arquivos abertos ao mesmo tempo.

Além de arquivos regulares, você pode abrir FDs para outros recursos, como pipes nomeados ou até mesmo sockets (via /dev/tcp). Por exemplo, exec 3<>/dev/tcp/host/porta abre uma conexão TCP bidirecional. Isso permite criar clientes de rede diretamente no shell, sem ferramentas externas como nc.

Lendo e escrevendo

Depois de abrir um FD, você precisa saber como ler e escrever nele. Para escrita, o redirecionamento padrão funciona: echo "mensagem" >&3 envia a saída para o FD 3. Para leitura, você pode usar o comando read com o redirecionamento <&3 ou usar o FD diretamente em comandos que aceitam entrada. Por exemplo:

exec 3<dados.txt
read -r linha <&3
echo "Li: $linha"
exec 3<&-

Nesse exemplo, abrimos o FD 3 para leitura de dados.txt, lemos uma linha com read e depois fechamos o FD. É uma maneira eficiente de ler arquivos linha por linha sem consumir a entrada padrão. Para ler múltiplas linhas, você pode usar um loop while com read redirecionado do FD.

Também é possível ler e escrever no mesmo FD se ele foi aberto no modo bidirecional (<>). Por exemplo, para uma conexão TCP, você pode enviar uma requisição e ler a resposta no mesmo FD. Isso é útil para scripts de teste de serviços web ou APIs.

Redirecionamento avançado

O Bash oferece várias formas de redirecionamento que vão além do simples > e <. Vamos destacar algumas:

  • Duplicação de FDs: Use exec 4>&1 para duplicar o FD 1 (saída padrão) no FD 4. Depois, você pode redirecionar temporariamente a saída padrão e restaurá-la com exec 1>&4. Isso é útil para capturar a saída de um bloco de comandos em uma variável.
  • Mover FDs: exec 3>&1- move o FD 1 para o FD 3, fechando o original. É menos comum, mas pode ser útil em situações específicas.
  • Here-documents e here-strings: Embora não sejam FDs, eles permitem fornecer entrada diretamente no script, o que pode ser combinado com FDs para criar fluxos complexos.
  • Redirecionamento de erro: 2>&1 envia erros para a mesma saída da saída padrão. Para enviar para um FD específico, use 2>&3.

Um padrão poderoso é usar FDs para capturar a saída de um comando sem afetar o fluxo principal. Por exemplo:

exec 3>&1          # salva a saída padrão no FD 3
exec 1>captura.txt  # redireciona a saída padrão para um arquivo
echo "Este texto vai para captura.txt"
exec 1>&3          # restaura a saída padrão
echo "Este texto vai para o terminal"
exec 3>&-          # fecha o FD 3

Exemplos

Vamos colocar em prática com um script que lê múltiplos arquivos e escreve em um log, tudo usando FDs:

#!/bin/bash
# Exemplo de uso de FDs avançados

# Abre FD 3 para escrita (log)
exec 3>>script.log

# Abre FD 4 para leitura (dados)
exec 4<dados.txt

# Lê todas as linhas do FD 4 e processa
while read -r linha <&4; do
    echo "Processando: $linha" >&3
done

# Fecha os FDs
exec 3>&-
exec 4<&-

echo "Script concluído."

Outro exemplo útil é usar FDs para comunicação bidirecional com um servidor TCP:

#!/bin/bash
# Cliente HTTP simples usando /dev/tcp

exec 3<>/dev/tcp/example.com/80

echo -e "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n" >&3

# Lê a resposta
response=$(cat <&3)

echo "Resposta recebida (primeiras linhas):"
echo "$response" | head -n 10

exec 3<&-

Esses exemplos mostram como os FDs avançados podem ser aplicados em tarefas reais, desde processamento de arquivos até comunicação de rede.

Boas práticas e observações finais

  • Sempre feche FDs que você abrir: Isso evita vazamento de recursos e mantém o script limpo.
  • Use nomes de FDs altos (3-9) para evitar conflito com os padrões: O Bash reserva os FDs 0, 1 e 2.
  • Teste se o FD foi aberto corretamente: Use if [[ -e /proc/$$/fd/3 ]]; then ... ou verifique com exec 3<arquivo || exit 1.
  • Prefira FDs para leitura em vez de sub-shells quando precisar manter o estado: Sub-shells podem perder variáveis, mas FDs mantêm o contexto.

Referências

Exercícios

  1. Escreva um script que abra o FD 3 para escrita em um arquivo chamado "saida.txt" e escreva a saída de ls -la nesse FD, sem afetar a saída padrão. Feche o FD ao final.

    ✓ Resposta:
    #!/bin/bash
    exec 3>saida.txt
    ls -la >&3
    exec 3>&-
    echo "Saída gravada em saida.txt"
  2. Explique a diferença entre exec 3>arquivo e exec 3>>arquivo e mostre um exemplo de uso de cada um.

    ✓ Resposta: exec 3>arquivo abre o arquivo para escrita, truncando (apagando) o conteúdo existente. Já exec 3>>arquivo abre para escrita em modo append, preservando o conteúdo e adicionando ao final. Exemplo: para um log que deve preservar histórico, use append; para um arquivo temporário que pode ser sobrescrito, use truncagem.
  3. Como você faria para ler a primeira linha de um arquivo usando um FD dedicado? Escreva o código.

    ✓ Resposta:
    exec 3<meu_arquivo.txt
    read -r primeira_linha <&3
    echo "Primeira linha: $primeira_linha"
    exec 3<&-
  4. Escreva um script que use um FD para capturar a saída de um comando e depois a exiba, sem usar variável. Dica: use o FD para redirecionar para um arquivo temporário e depois ler.

    ✓ Resposta:
    #!/bin/bash
    exec 3>temp_captura.txt
    date >&3
    exec 3>&-
    exec 3<temp_captura.txt
    cat <&3
    exec 3<&-
    rm temp_captura.txt
  5. Qual é a utilidade de exec 3<>/dev/tcp/host/porta? Dê um exemplo prático de uso.

    ✓ Resposta: Isso abre uma conexão TCP bidirecional com o host na porta especificada, permitindo enviar e receber dados como se fosse um socket. Exemplo: verificar se uma porta está aberta em um servidor, enviando uma requisição e lendo a resposta, como em um teste de conectividade.