Substituição de comandos
Esta aula aborda a substituição de comandos em shell script, explicando as sintaxes moderna ($(comando)) e legada (backticks), aninhamento e casos de uso práticos. O aluno aprenderá a capturar saídas de comandos para uso em variáveis e expressões, essencial para automação de tarefas.
A substituição de comandos é um recurso fundamental em shell script que permite capturar a saída de um comando e utilizá-la como parte de outro comando, em uma variável ou em uma expressão. Isso possibilita criar scripts dinâmicos que reagem a informações obtidas em tempo de execução, como listar arquivos, obter datas ou processar resultados de consultas.
Existem duas formas de realizar substituição de comandos no Bash: a sintaxe moderna $(comando) e a sintaxe legada com backticks (`comando`). Embora ambas funcionem, a primeira é preferível por ser mais clara, permitir aninhamento fácil e ter comportamento mais previsível. Nesta aula, veremos detalhadamente cada uma, como aninhar substituições e diversos casos de uso práticos.
$(comando)
A sintaxe $(comando) é a forma moderna e recomendada de substituição de comandos no Bash. Ela executa o comando dentro dos parênteses e substitui toda a expressão pela saída do comando (stdout). Essa sintaxe é mais legível, especialmente quando o comando contém parênteses ou aspas, e permite aninhamento sem ambiguidade.
Exemplo básico: armazenar a data atual em uma variável.
#!/bin/bash
data_atual=$(date +%d/%m/%Y)
echo "Hoje é $data_atual"
Outro exemplo: contar o número de arquivos no diretório atual.
#!/bin/bash
num_arquivos=$(ls -1 | wc -l)
echo "Existem $num_arquivos arquivos no diretório atual."
Note que a substituição de comandos pode ser usada diretamente em argumentos de outros comandos, sem necessidade de variável intermediária:
#!/bin/bash
echo "O diretório home é: $(echo $HOME)"
Backticks (legado)
Antes da introdução de $(...), a substituição de comandos era feita com backticks (`comando`). Embora ainda funcione em shells modernos, seu uso é desencorajado por diversos motivos: difícil aninhamento, problemas com caracteres especiais (como aspas e escapes) e menor legibilidade.
Exemplo equivalente ao anterior com backticks:
#!/bin/bash
data_atual=`date +%d/%m/%Y`
echo "Hoje é $data_atual"
O principal problema com backticks surge ao tentar aninhar substituições. Por exemplo, para obter a saída de um comando que contém backticks, é necessário escapar os backticks internos com barra invertida, o que torna o código confuso e propenso a erros. Além disso, a interpretação de aspas dentro de backticks é inconsistente entre shells. Por esses motivos, a sintaxe $(...) é sempre preferível.
Aninhamento
Uma das grandes vantagens da sintaxe $(...) é a facilidade de aninhar substituições de comandos. Isso significa que podemos usar o resultado de uma substituição como parte de outra, sem necessidade de escapes complexos.
Exemplo: obter o nome do diretório pai do diretório atual.
#!/bin/bash
pai=$(basename $(dirname $(pwd)))
echo "O diretório pai é: $pai"
Neste exemplo, $(pwd) retorna o caminho completo, $(dirname ...) extrai o diretório pai e $(basename ...) pega apenas o nome final. O Bash processa de dentro para fora: primeiro $(pwd), depois $(dirname ...), e por último $(basename ...).
Com backticks, o mesmo aninhamento exigiria escapes:
#!/bin/bash
pai=`basename \`dirname \`pwd\`\``
echo "O diretório pai é: $pai"
Como se vê, a legibilidade fica comprometida. Portanto, para aninhamentos, sempre use $(...).
Casos de uso
A substituição de comandos é amplamente utilizada em scripts para capturar informações do sistema, processar listas de arquivos, gerar nomes dinâmicos e integrar com outras ferramentas. Abaixo, alguns casos comuns.
1. Obter informações do sistema:
#!/bin/bash
kernel=$(uname -r)
echo "Versão do kernel: $kernel"
usuarios=$(who | wc -l)
echo "Usuários logados: $usuarios"
2. Processar listas de arquivos:
#!/bin/bash
# Listar apenas arquivos .txt
txt_files=$(ls *.txt 2>/dev/null)
echo "Arquivos .txt:"
echo "$txt_files"
3. Gerar nomes de arquivos com timestamp:
#!/bin/bash
timestamp=$(date +%Y%m%d_%H%M%S)
nome_arquivo="backup_$timestamp.tar.gz"
echo "Criando arquivo: $nome_arquivo"
# tar -czf "$nome_arquivo" /dados
4. Uso em condições:
#!/bin/bash
if [ "$(whoami)" = "root" ]; then
echo "Você é root."
else
echo "Você não é root."
fi
5. Combinar com outros comandos:
#!/bin/bash
# Contar linhas de um arquivo específico
linhas=$(wc -l < /etc/passwd)
echo "O arquivo /etc/passwd tem $linhas linhas."
Boas práticas
- Sempre utilize
$(comando)em vez de backticks, a menos que esteja mantendo scripts legados. - Aspas duplas ao redor da substituição (
"$(comando)") preservam espaços e quebras de linha, evitando problemas com word splitting. - Evite substituições desnecessárias dentro de comandos que já aceitam pipes ou redirecionamentos; às vezes um pipe é mais claro.
- Para comandos que produzem saída longa, considere armazenar em uma variável e depois processar com loops ou ferramentas como
sedeawk.
Referências
- GNU Bash Manual - Command Substitution
- Advanced Bash-Scripting Guide - Command Substitution
- Bash Hackers Wiki - Command Substitution
- Greg's Wiki - BashFAQ/002: How can I store the output of a command in a variable?
- Stack Overflow - Command substitution: backticks vs dollar sign
Exercícios
-
Crie um script que armazene na variável
dataa data atual no formato "AAAA-MM-DD" e exiba uma mensagem: "Hoje é AAAA-MM-DD".✓ Resposta:#!/bin/bash data=$(date +%Y-%m-%d) echo "Hoje é $data" -
Escreva um comando que exiba o número de diretórios (não arquivos) no diretório atual. Use substituição de comandos e o comando
ls -d */.✓ Resposta:#!/bin/bash num_dirs=$(ls -d */ 2>/dev/null | wc -l) echo "Número de diretórios: $num_dirs" -
Utilizando aninhamento de substituição de comandos, crie um script que exiba o nome do diretório atual (apenas o nome, não o caminho completo).
✓ Resposta:#!/bin/bash nome_dir=$(basename $(pwd)) echo "Você está no diretório: $nome_dir" -
Escreva um script que verifique se o usuário atual é "root" usando substituição de comandos e exiba uma mensagem apropriada.
✓ Resposta:#!/bin/bash if [ "$(whoami)" = "root" ]; then echo "Você é root." else echo "Você não é root." fi -
Crie um script que defina uma variável
arquivocom o nome do primeiro arquivo .txt encontrado no diretório atual (usels *.txt 2>/dev/null | head -1) e exiba seu conteúdo comcat. Se não houver arquivo .txt, exiba uma mensagem de erro.✓ Resposta:#!/bin/bash arquivo=$(ls *.txt 2>/dev/null | head -1) if [ -n "$arquivo" ]; then cat "$arquivo" else echo "Nenhum arquivo .txt encontrado." fi