A substituição de processo é um recurso avançado do Bash que permite tratar a saída de um comando como se fosse um arquivo temporário. Isso é extremamente útil quando você precisa passar a saída de um comando para outro que espera um arquivo como argumento, sem criar arquivos temporários no disco. A sintaxe básica é <(comando), que gera um descritor de arquivo virtual (geralmente um pipe ou um arquivo em /dev/fd) que pode ser lido pelo comando que o recebe.

Nesta aula, vamos explorar como usar a substituição de processo para comparar saídas de comandos, entender seus casos de uso mais comuns e ver exemplos práticos que demonstram seu poder. Ao final, você será capaz de integrar essa técnica em seus scripts para torná-los mais eficientes e elegantes.

<(comando)

A sintaxe <(comando) é chamada de substituição de processo. Quando o Bash encontra essa expressão, ele executa o comando entre parênteses e conecta sua saída a um descritor de arquivo (geralmente um pipe ou um arquivo em /dev/fd). O resultado é um caminho de arquivo (como /dev/fd/63) que pode ser usado como argumento para outros comandos. Esse arquivo virtual contém a saída do comando, e pode ser lido uma única vez (como um pipe).

É importante notar que a substituição de processo é uma extensão do Bash, não disponível em shells POSIX estritamente (como o sh). Ela é especialmente útil quando você precisa passar a saída de um comando para outro que espera um arquivo, como diff, comm, grep, entre outros. Veja um exemplo simples:

# Exemplo: comparar a listagem de dois diretórios
diff <(ls dir1) <(ls dir2)

Nesse caso, o diff recebe dois "arquivos" que contêm as saídas de ls dir1 e ls dir2, respectivamente, e mostra as diferenças entre eles. Sem a substituição de processo, você precisaria criar arquivos temporários e depois removê-los.

Comparando saídas

Um dos usos mais frequentes da substituição de processo é comparar a saída de dois comandos. O comando diff é um exemplo clássico, mas também podemos usar comm, cmp e até mesmo grep para processar saídas como arquivos. A vantagem é que você não precisa armazenar a saída em arquivos temporários, evitando poluição do diretório e problemas de concorrência.

Vamos ver um exemplo com comm, que compara dois arquivos ordenados linha por linha e mostra as linhas exclusivas de cada um ou comuns a ambos. Para usar comm, os arquivos precisam estar ordenados, então podemos usar sort na substituição:

# Comparar listas de usuários em dois sistemas
comm -12 <(cut -d: -f1 /etc/passwd | sort) <(cut -d: -f1 /etc/group | sort)

Outro exemplo é usar cmp para verificar se duas saídas são idênticas, embora diff -q seja mais comum. A substituição de processo permite que você faça comparações sem criar arquivos intermediários, tornando seus scripts mais limpos e seguros.

Casos de uso

A substituição de processo é útil em várias situações:

  • Comparar saídas de comandos: como vimos, diff, comm, cmp podem receber saídas de comandos diretamente.
  • Processar múltiplos arquivos: quando um comando aceita vários arquivos de entrada, você pode fornecer saídas de outros comandos como se fossem arquivos.
  • Evitar arquivos temporários: em scripts que precisam de arquivos temporários, a substituição de processo elimina a necessidade de criar e limpar esses arquivos, reduzindo erros e melhorando a legibilidade.
  • Compor pipelines complexos: você pode combinar substituição de processo com pipes e redirecionamentos para criar fluxos de dados sofisticados.
  • Em loops e processamento em lote: por exemplo, para processar cada linha de uma saída como argumento para um comando que espera um arquivo.

Um caso comum é usar while read para iterar sobre a saída de um comando, mas a substituição de processo pode ser usada quando você precisa que o comando que lê o arquivo seja executado de forma síncrona, como em comparações.

Exemplos

Vamos ver alguns exemplos práticos e comentados:

# Exemplo 1: Verificar se dois comandos produzem a mesma saída
if diff -q <(echo "olá") <(echo "olá") >/dev/null; then
    echo "As saídas são iguais"
else
    echo "As saídas são diferentes"
fi
# Exemplo 2: Processar a saída de um comando como arquivo de entrada para outro
# Contar quantas linhas da saída de 'ps' contêm 'bash'
grep -c bash <(ps aux)
# Exemplo 3: Usar substituição de processo com 'while read' para processar linhas
while IFS= read -r linha; do
    echo "Linha: $linha"
done < <(ls -la)
# Exemplo 4: Comparar a saída de dois comandos com 'comm'
comm -3 <(printf "a\nb\nc\n" | sort) <(printf "b\nc\nd\n" | sort)

No Exemplo 3, observe o uso de < <(comando) para redirecionar a entrada do loop while para a saída do comando. Isso é uma combinação de redirecionamento e substituição de processo, muito útil.

É importante lembrar que a substituição de processo cria um pipe, portanto a saída do comando é consumida uma única vez. Se você precisar ler a saída múltiplas vezes, talvez seja melhor armazenar em uma variável ou arquivo temporário.

Boas práticas

  • Use substituição de processo quando precisar passar a saída de um comando como arquivo, mas evite usá-la em excesso para não prejudicar a legibilidade.
  • Combine com diff, comm e outros comandos que aceitam arquivos para criar comparações poderosas.
  • Em scripts portáveis, lembre-se de que a substituição de processo é específica do Bash; se precisar de portabilidade, considere usar arquivos temporários com trap para limpeza.
  • Tenha cuidado com o consumo de memória e descritores de arquivo: cada substituição de processo usa um descritor, e muitos podem esgotar o limite.

Referências

Exercícios

  1. Escreva um comando que use substituição de processo para comparar a saída de ls -l em dois diretórios diferentes com diff e exiba somente as linhas que diferem (sem contexto).

    ✓ Resposta: diff --suppress-common-lines <(ls -l dir1) <(ls -l dir2)
  2. Usando substituição de processo, conte quantas linhas da saída de ps aux contêm a palavra "root" (use grep).

    ✓ Resposta: grep -c root <(ps aux)
  3. Crie um script que use while read para ler a saída de find . -name "*.txt" e imprima cada arquivo encontrado, usando substituição de processo.

    ✓ Resposta:
    while IFS= read -r arquivo; do
        echo "$arquivo"
    done < <(find . -name "*.txt")
  4. Explique por que a substituição de processo não é adequada se você precisar ler a saída do comando mais de uma vez, e sugira uma alternativa.

    ✓ Resposta: A substituição de processo usa um pipe, que é consumido após a leitura. Se precisar ler a saída várias vezes, armazene em uma variável ou em um arquivo temporário. Por exemplo: saida=$(ls dir1); echo "$saida"; echo "$saida".
  5. Implemente um comando que use comm para mostrar as linhas que estão presentes apenas na saída de ls dir1 (e não em ls dir2), assumindo que as saídas são ordenadas.

    ✓ Resposta: comm -23 <(ls dir1 | sort) <(ls dir2 | sort)