Variáveis em Bash: Fundamentos e Boas Práticas
Variáveis são contêineres que armazenam dados na memória durante a execução de um script. Em Bash, não é necessário declarar o tipo de dado explicitamente — a linguagem realiza conversão dinâmica conforme o contexto. A sintaxe para atribuir uma variável é simples: nome_variavel=valor, sem espaços antes ou depois do sinal de igualdade. Quando você precisa acessar o valor armazenado, utilize o símbolo $ precedendo o nome.
A diferença entre usar $variavel e ${variavel} é sutil mas importante. A primeira forma funciona na maioria dos casos, mas a segunda é mais segura quando a variável é seguida de caracteres alfanuméricos. Por exemplo, se você tem nome=João e escreve echo $nome_completo, Bash procurará por uma variável chamada nome_completo, não nome. Já echo ${nome}_completo retorna corretamente João_completo.
#!/bin/bash
# Atribuição simples
mensagem="Bem-vindo ao Bash"
idade=25
numero_pi=3.14159
# Acessando variáveis
echo "Mensagem: $mensagem"
echo "Idade: $idade"
# Forma segura com chaves
arquivo="dados"
echo "Arquivo: ${arquivo}.txt" # dados.txt
# Variáveis de ambiente (já existem no sistema)
echo "Usuário atual: $USER"
echo "Diretório home: $HOME"
echo "Shell utilizado: $SHELL"
# Comando de substituição (armazena resultado de um comando)
data_atual=$(date +%d/%m/%Y)
echo "Data: $data_atual"
# Outro formato (mais antigo, menos preferido)
lista_arquivos=`ls -la`
echo "$lista_arquivos"
Variáveis Especiais e Argumentos
Bash fornece variáveis automáticas que recebem informações sobre o script e seus argumentos. A variável $0 contém o nome do script, $1, $2, etc., armazenam o primeiro, segundo argumentos passados na linha de comando, e $# indica a quantidade total de argumentos. A variável $@ expande para todos os argumentos, preservando espaços, enquanto $* expande como uma string única.
#!/bin/bash
# Script que processa argumentos
echo "Nome do script: $0"
echo "Número de argumentos: $#"
echo "Primeiro argumento: $1"
echo "Segundo argumento: $2"
# Todos os argumentos
echo "Todos os argumentos (\$@): $@"
# Valor de saída do comando anterior
resultado=$(echo "Teste")
echo "Status anterior: $?" # 0 = sucesso, não-zero = erro
# Variáveis globais vs locais
variavel_global="Sou global"
funcao_teste() {
local variavel_local="Sou local"
echo "$variavel_global" # Funciona
echo "$variavel_local" # Funciona
}
funcao_teste
# echo "$variavel_local" # Erro: variável não existe aqui
Condicionais: Tomando Decisões no Script
Estruturas condicionais permitem que seu script execute diferentes blocos de código baseado em testes lógicos. A estrutura fundamental é o if, que avalia uma condição e executa um bloco se ela for verdadeira. Em Bash, uma condição é considerada verdadeira quando o comando retorna status 0, e falsa quando retorna qualquer valor diferente de zero. Você pode testar condições numéricas, de strings, de existência de arquivos e muito mais.
O Bash oferece dois formatos para testes: [ ] (mais portável e tradicional) e [[ ]] (mais moderno e seguro, específico do Bash). A forma [[ ]] trata melhor variáveis vazias e suporta expressões regulares, tornando-a preferível na maioria dos casos. Dentro dos colchetes, usamos operadores como -eq (igual numérico), -ne (não igual numérico), -lt (menor que), -gt (maior que), -z (string vazia) e -n (string não-vazia).
#!/bin/bash
idade=18
# Estrutura if-else simples
if [[ $idade -ge 18 ]]; then
echo "Você é maior de idade"
else
echo "Você é menor de idade"
fi
# Testando strings
nome="Maria"
if [[ $nome == "Maria" ]]; then
echo "Olá, Maria!"
fi
# Testando se uma variável está vazia
entrada=""
if [[ -z $entrada ]]; then
echo "Entrada está vazia"
fi
# Testando existência de arquivo
if [[ -f /etc/passwd ]]; then
echo "Arquivo /etc/passwd existe"
fi
# Testando se é um diretório
if [[ -d /home ]]; then
echo "/home é um diretório"
fi
# Múltiplas condições com AND (&&) e OR (||)
if [[ $idade -ge 18 ]] && [[ $nome == "Maria" ]]; then
echo "Maria é maior de idade"
fi
if [[ $idade -lt 18 ]] || [[ $nome == "João" ]]; then
echo "É menor de idade OU se chama João"
fi
Estruturas if-elif-else e Nested
Quando você precisa testar múltiplas condições sequenciais, use elif (else if). O script avalia cada condição na ordem até encontrar uma verdadeira. É importante notar que apenas o primeiro bloco verdadeiro é executado; os demais são ignorados. Para lógica mais complexa, você pode aninhar (nested) estruturas if dentro de outras.
#!/bin/bash
nota=7.5
if [[ $nota -ge 9 ]]; then
echo "Conceito: A"
elif [[ $nota -ge 8 ]]; then
echo "Conceito: B"
elif [[ $nota -ge 7 ]]; then
echo "Conceito: C"
elif [[ $nota -ge 6 ]]; then
echo "Conceito: D"
else
echo "Conceito: F (Reprovado)"
fi
# Exemplo nested
idade=25
renda=3000
if [[ $idade -ge 18 ]]; then
if [[ $renda -ge 2000 ]]; then
echo "Aprovado para empréstimo"
else
echo "Renda insuficiente"
fi
else
echo "Maior de idade é requisito obrigatório"
fi
Case: Alternativa Limpa para Múltiplas Condições
Quando você precisa comparar uma variável contra vários valores fixos, a estrutura case é mais limpa e legível que múltiplos elif. Cada padrão (pattern) é testado sequencialmente, e o primeiro que combinar tem seu bloco executado. Use ;; para encerrar cada caso e *) como padrão padrão (equivalente ao else).
#!/bin/bash
dia=$(date +%A)
case $dia in
Monday)
echo "Início da semana de trabalho"
;;
Friday)
echo "Quase fim de semana!"
;;
Saturday|Sunday)
echo "Fim de semana - descanse!"
;;
*)
echo "Dia comum da semana"
;;
esac
# Case com padrões e variáveis
operacao=$1
numero1=$2
numero2=$3
case $operacao in
add|somar)
echo $((numero1 + numero2))
;;
sub|subtrair)
echo $((numero1 - numero2))
;;
mul|multiplicar)
echo $((numero1 * numero2))
;;
*)
echo "Operação desconhecida"
;;
esac
Laços: Iterando sobre Dados
Laços permitem executar um bloco de código múltiplas vezes. O Bash oferece várias estruturas: for, while e until. O for é ideal quando você conhece previamente quantas iterações serão necessárias ou quando itera sobre uma coleção de itens. O while continua executando enquanto uma condição for verdadeira, sendo útil quando a quantidade de iterações é desconhecida antecipadamente. O until funciona de forma inversa ao while, repetindo enquanto a condição for falsa.
Dentro de um laço, as palavras-chave break e continue oferecem controle fino. break encerra o laço imediatamente, enquanto continue pula para a próxima iteração. Essas construções evitam lógica complexa aninhada e tornam o código mais legível.
For: Iteração com Coleções
O for tradicional em Bash itera sobre uma lista de palavras. Cada iteração atribui a próxima palavra à variável de controle. A lista pode vir de uma expansão de glob (*.txt), de uma variável contendo múltiplas palavras, ou de um comando que produz várias linhas.
#!/bin/bash
# Iterando sobre uma lista explícita
for fruta in maçã banana laranja; do
echo "Fruta: $fruta"
done
# Iterando sobre arquivos
for arquivo in *.txt; do
if [[ -f $arquivo ]]; then
echo "Processando: $arquivo"
# wc -l "$arquivo"
fi
done
# Iterando sobre resultado de um comando
for usuario in $(cat /etc/passwd | cut -d: -f1 | head -5); do
echo "Usuário: $usuario"
done
# For com range numérico (Bash 4+)
for i in {1..5}; do
echo "Número: $i"
done
# For com range numérico e passo
for i in {0..20..5}; do
echo "Valor: $i"
done
# For com aritmética (C-style)
for ((i=1; i<=3; i++)); do
echo "Iteração $i"
done
# Acessando índices em array
frutas=("maçã" "banana" "laranja" "uva")
for ((i=0; i<${#frutas[@]}; i++)); do
echo "$i: ${frutas[$i]}"
done
While e Until: Iteração Condicional
while é útil quando a quantidade de iterações depende de uma condição dinâmica, como ler linhas de um arquivo ou processar input do usuário. until funciona identicamente, mas inverte a lógica: continua repetindo enquanto a condição for falsa, parando quando se torna verdadeira.
#!/bin/bash
# While simples
contador=1
while [[ $contador -le 5 ]]; do
echo "Contador: $contador"
contador=$((contador + 1))
done
# While lendo arquivo linha por linha
while IFS= read -r linha; do
echo "Linha: $linha"
done < /etc/hostname
# While com entrada do usuário
echo "Digite 'sair' para encerrar:"
while [[ true ]]; do
read -p "Comando: " comando
if [[ $comando == "sair" ]]; then
break
fi
echo "Você digitou: $comando"
done
# Until (oposto do while)
numero=1
until [[ $numero -gt 3 ]]; do
echo "Número: $numero"
numero=$((numero + 1))
done
# While com continue
for i in {1..10}; do
if [[ $((i % 2)) -eq 0 ]]; then
continue # Pula números pares
fi
echo "Número ímpar: $i"
done
Break e Continue em Contexto
break sai imediatamente do laço mais próximo, enquanto break 2 sai de dois laços aninhados. continue pula para a próxima iteração do laço atual. Essas construções evitam acúmulo de lógica condicional e tornam o fluxo mais claro.
#!/bin/bash
# Break com laços aninhados
for i in {1..3}; do
for j in {1..3}; do
if [[ $j -eq 2 ]]; then
break 2 # Sai dos dois laços
fi
echo "i=$i, j=$j"
done
done
# Continue prático: processar apenas linhas válidas
while IFS= read -r linha; do
# Ignora linhas vazias ou com comentários
[[ -z $linha ]] && continue
[[ $linha =~ ^# ]] && continue
echo "Processando: $linha"
done < config.txt
Funções: Reutilizando Código
Funções são blocos de código nomeados que podem ser definidos uma vez e chamados múltiplas vezes. Elas aceitam argumentos (acessíveis via $1, $2, etc., assim como no script principal) e podem retornar um valor usando return. Funções melhoram legibilidade, reduzem duplicação de código e facilitam testes e manutenção.
Em Bash, existem duas sintaxes para definir funções. A primeira, function nome { }, é mais verbosa. A segunda, nome() { }, é mais concisa e preferida na comunidade. Ambas funcionam identicamente. Importante: a função deve ser definida antes de ser chamada.
Definição, Argumentos e Retorno
Uma função recebe argumentos na chamada e acessa-os através de $1, $2, $#, $@ — exatamente como um script recebe argumentos de linha de comando. O return especifica um código de saída numérico (0 a 255), onde 0 indica sucesso. Para retornar dados ao invés de apenas um código, use echo ou printf dentro da função e capture o output com $(funcao).
#!/bin/bash
# Função simples sem argumentos
saudacao() {
echo "Olá, bem-vindo!"
}
saudacao
# Função com argumentos
saudar_pessoa() {
local nome=$1
local sobrenome=$2
echo "Bem-vindo, $nome $sobrenome!"
}
saudar_pessoa "João" "Silva"
# Função que retorna um valor
calcular_dobro() {
local numero=$1
echo $((numero * 2))
}
resultado=$(calcular_dobro 5)
echo "Dobro de 5 é: $resultado"
# Função que retorna código de status
arquivo_existe() {
local caminho=$1
if [[ -f $caminho ]]; then
return 0 # Sucesso
else
return 1 # Falha
fi
}
if arquivo_existe "/etc/passwd"; then
echo "Arquivo existe"
else
echo "Arquivo não existe"
fi
Variáveis Locais e Escopo
Por padrão, variáveis em Bash são globais — acessíveis em qualquer lugar do script. Dentro de funções, declare variáveis com local para limitar seu escopo apenas à função. Isso evita conflitos acidentais com variáveis do mesmo nome em outras partes do código e torna a função mais autossuficiente e reutilizável.
#!/bin/bash
contador_global=0
incrementar() {
local contador_local=10
contador_global=$((contador_global + 1))
echo "Local: $contador_local, Global: $contador_global"
}
incrementar
incrementar
echo "Valor global fora da função: $contador_global"
# Modificando variáveis globais dentro da função
valor_importante="original"
modificar() {
valor_importante="modificado"
}
echo "Antes: $valor_importante"
modificar
echo "Depois: $valor_importante"
Funções com Múltiplos Argumentos e Validação
Funções bem construídas validam seus argumentos e tratam erros apropriadamente. Uma prática comum é verificar se o número correto de argumentos foi passado e exibir uma mensagem de uso se houver problema.
#!/bin/bash
# Função com validação
copiar_arquivo() {
if [[ $# -ne 2 ]]; then
echo "Uso: copiar_arquivo <origem> <destino>"
return 1
fi
local origem=$1
local destino=$2
if [[ ! -f $origem ]]; then
echo "Erro: arquivo de origem não existe"
return 1
fi
cp "$origem" "$destino"
echo "Arquivo copiado com sucesso"
}
# Função que processa variadic arguments
somar_numeros() {
if [[ $# -eq 0 ]]; then
echo "Nenhum número fornecido"
return 1
fi
local soma=0
for numero in "$@"; do
soma=$((soma + numero))
done
echo $soma
}
resultado=$(somar_numeros 10 20 30 40)
echo "Soma: $resultado"
# Função que itera sobre argumentos
listar_argumentos() {
local contador=1
for arg in "$@"; do
echo "Argumento $contador: $arg"
contador=$((contador + 1))
done
}
listar_argumentos "primeiro" "segundo" "terceiro"
Conclusão
Shell scripting com Bash é uma habilidade essencial para qualquer profissional de tecnologia. Os quatro pilares abordados — variáveis, condicionais, laços e funções — formam a base para automatizar tarefas, processar dados e construir ferramentas poderosas diretamente no terminal. Dominar esses conceitos permite escrever scripts que são não apenas funcionais, mas também legíveis e fáceis de manter.
A prática deliberada é crucial: comece com scripts simples que combinam uma ou duas dessas estruturas, depois avance para casos mais complexos. Leia exemplos de scripts reais (especialmente em repositórios como /usr/local/bin ou projetos open source) para internalizar padrões idiomáticos do Bash. Finalmente, sempre teste seus scripts e trate erros explicitamente — um bom script não apenas faz o que deveria fazer, mas também falha de forma previsível e informativa quando algo dá errado.