Substituição de comandos
A aula aborda a substituição de comandos em shell script, explicando as sintaxes $(comando) e backticks, o aninhamento de substituições e casos de uso práticos. O foco é ensinar como capturar a saída de comandos para uso em variáveis e expressões.
A substituição de comandos é um recurso fundamental em shell script que permite executar um comando e utilizar sua saída como parte de outro comando, atribuição de variável ou expressão. Em vez de apenas exibir o resultado na tela, você pode capturá-lo e armazená-lo para processamento posterior. Essa técnica é amplamente usada para criar scripts dinâmicos e automatizados.
Existem duas formas de realizar substituição de comandos: a sintaxe moderna com $(comando) e a sintaxe legada com backticks (`comando`). Embora ambas funcionem, a primeira é recomendada por ser mais legível e permitir aninhamento fácil. Nesta aula, exploraremos cada uma, suas diferenças e como utilizá-las em situações reais.
$(comando)
A sintaxe $(comando) é a forma moderna e preferida de substituição de comandos em bash. Ela executa o comando dentro dos parênteses e substitui toda a expressão pelo resultado da execução. O comando pode ser qualquer comando shell válido, incluindo pipelines, redirecionamentos e até outras substituições.
Essa sintaxe é mais clara, especialmente quando aninhada, pois os parênteses são visualmente distintos. Além disso, o bash interpreta corretamente aspas e caracteres especiais dentro de $(), reduzindo erros. Veja um exemplo simples:
#!/bin/bash
# Captura a data atual
data_atual=$(date)
echo "Hoje é: $data_atual"No exemplo acima, o comando date é executado e sua saída (a data atual) é atribuída à variável data_atual. Depois, a variável é usada em um echo.
Outro exemplo comum é usar a saída de um comando como argumento de outro:
#!/bin/bash
# Lista arquivos .txt e conta as linhas de cada um
for arquivo in $(ls *.txt); do
wc -l "$arquivo"
doneAtenção: esse loop pode quebrar se nomes de arquivos contiverem espaços. Para maior segurança, use while read ou find com -print0.
Backticks (legado)
A sintaxe com backticks (`comando`) é a forma original de substituição de comandos, herdada do shell Bourne. Ela ainda funciona no bash, mas é considerada legada e seu uso é desencorajado em scripts modernos. O problema principal é a legibilidade: backticks são facilmente confundidos com aspas simples e dificultam o aninhamento.
Exemplo equivalente ao anterior:
#!/bin/bash
data_atual=`date`
echo "Hoje é: $data_atual"Quando você precisa aninhar substituições, os backticks exigem escapes com barra invertida, tornando o código confuso:
# Aninhamento com backticks (legado)
resultado=`echo \`whoami\``
echo "Usuário: $resultado"Já com $(), o mesmo aninhamento fica mais limpo:
resultado=$(echo $(whoami))
echo "Usuário: $resultado"Por esses motivos, sempre prefira $(comando) em novos scripts.
Aninhamento
O aninhamento de substituições de comandos ocorre quando uma substituição é colocada dentro de outra. Isso é útil para processar saídas em múltiplas etapas. Com $(), o aninhamento é natural e não requer escapes especiais.
Exemplo: obter o nome do diretório pai do diretório atual.
#!/bin/bash
# Caminho completo do diretório atual
caminho_atual=$(pwd)
# Nome do diretório pai (último componente do caminho)
dir_pai=$(basename $(dirname "$caminho_atual"))
echo "Diretório pai: $dir_pai"Note que dirname retorna o caminho do diretório pai, e basename extrai apenas o nome final. O aninhamento evita variáveis intermediárias.
Outro exemplo: contar quantos usuários estão logados no sistema.
#!/bin/bash
num_usuarios=$(who | wc -l)
echo "Usuários logados: $num_usuarios"Aqui, who lista os usuários e wc -l conta as linhas. Tudo dentro de $().
Para aninhamentos mais complexos, você pode usar substituições dentro de substituições, como em:
#!/bin/bash
# Pega o primeiro PID do processo bash
pid=$(ps aux | grep bash | head -1 | awk '{print $2}')
echo "PID do primeiro bash: $pid"Embora funcione, lembre-se de que o grep pode encontrar a si mesmo; use grep [b]ash para evitar.
Casos de uso
A substituição de comandos é usada em inúmeras situações. Abaixo, alguns casos comuns em scripts do dia a dia.
- Atribuição de variáveis dinâmicas: armazenar saídas de comandos como data, nome do usuário, caminhos etc.
- Construção de argumentos: usar a saída de um comando como argumento para outro, por exemplo,
vim $(find . -name "*.txt"). - Laços de repetição: iterar sobre a saída de um comando, como listar arquivos ou processos.
- Testes condicionais: verificar se um comando produziu saída, por exemplo,
if [ -n "$(comando)" ]. - Geração de nomes de arquivos: criar nomes baseados em data/hora, como
backup_$(date +%Y%m%d).tar.gz.
Exemplo prático: um script que faz backup de um diretório e nomeia o arquivo com a data.
#!/bin/bash
origem="/home/usuario/documentos"
destino="/backups"
arquivo="backup_$(date +%Y%m%d_%H%M%S).tar.gz"
tar -czf "$destino/$arquivo" "$origem"
echo "Backup criado: $destino/$arquivo"Outro exemplo: verificar se um serviço está rodando.
#!/bin/bash
servico="apache2"
if systemctl is-active --quiet "$servico"; then
echo "$servico está ativo"
else
echo "$servico não está ativo"
fiNote que aqui usamos o status de saída do comando, não a substituição. Mas poderíamos capturar a saída com $(systemctl is-active "$servico") e comparar com "active".
Boas práticas e observações finais
- Prefira
$(comando)a backticks em scripts novos. - Ao usar substituição em loops, cuidado com espaços em nomes de arquivos. Considere usar
while IFS= read -rcomfind. - Evite substituições desnecessárias; às vezes, a saída do comando pode ser usada diretamente.
- Para comandos que produzem saída longa, avalie se precisa armazenar tudo em memória.
- Lembre-se de que a substituição de comandos remove as novas linhas finais, a menos que esteja dentro de aspas duplas.
Referências
- GNU Bash Manual: Command Substitution
- Advanced Bash-Scripting Guide: Command Substitution
- Greg's Wiki: Command Substitution
- Stack Overflow: Benefits of Command Substitution
- Devhints: Bash scripting cheatsheet
Exercícios
Escreva um script que capture a saída do comando
whoamie a exiba em uma mensagem.✓ Resposta:#!/bin/bash usuario=$(whoami) echo "O usuário atual é: $usuario"Crie um script que armazene a lista de arquivos .txt do diretório atual em uma variável e depois exiba cada nome. Use substituição de comandos.
✓ Resposta:#!/bin/bash arquivos=$(ls *.txt 2>/dev/null) for arq in $arquivos; do echo "Arquivo: $arq" doneNota: Esta abordagem falha se nomes contiverem espaços. Versão mais segura:
#!/bin/bash while IFS= read -r -d '' arq; do echo "Arquivo: $arq" done < <(find . -maxdepth 1 -name "*.txt" -print0)Escreva um script que use aninhamento de substituição para obter o nome do diretório home do usuário root (dica: use
getent passwd root | cut -d: -f6).✓ Resposta:#!/bin/bash home_root=$(getent passwd root | cut -d: -f6) echo "Diretório home do root: $home_root"Aninhamento não é necessário aqui, mas poderíamos fazer:
home_root=$(echo $(getent passwd root | cut -d: -f6))Crie um script que verifique se o comando
curlestá instalado. Use substituição de comandos para capturar a saída dewhich curle teste se a variável não está vazia.✓ Resposta:#!/bin/bash curl_path=$(which curl 2>/dev/null) if [ -n "$curl_path" ]; then echo "curl está instalado em: $curl_path" else echo "curl não está instalado" fiEscreva um script que exiba a data e hora atuais no formato "AAAA-MM-DD HH:MM:SS" usando substituição de comandos e o comando
date.✓ Resposta:#!/bin/bash data_hora=$(date '+%Y-%m-%d %H:%M:%S') echo "Data e hora: $data_hora"