Shell Scripting Pratico: Funcoes, Arrays e Tecnicas de Operacao com Arquivos
Depois de dominar os fundamentos de shell scripting, e hora de construir habilidades avancadas praticas. Este guia cobre funcoes, arrays, operacoes com arquivos, tratamento de erros e exemplos de automacao do mundo real para ajuda-lo a escrever scripts Bash de nivel profissional.
O Que Voce Vai Aprender
- Como definir funcoes com variaveis locais, valores de retorno e tratamento de erros
- Como criar e manipular arrays indexados e associativos
- Tecnicas para ler arquivos linha por linha, escrever saida e processar dados CSV
- Como
set -euo pipefailetrapconstroem tratamento robusto de erros em scripts - Tres scripts de automacao do mundo real: backup do sistema, monitoramento de logs e organizador de arquivos
Funcoes
Conclusao: Em funcoes: local para escopo, echo para retornar valores, return 1 para sinalizar erros.
Definindo e Chamando Funcoes
Funcao Basica
#!/bin/bash
greet() {
echo "Ola, $1!"
}
greet "Alice"
greet "Bob"Funcao com Valor de Retorno
#!/bin/bash
square() {
local num=$1
local result=$((num * num))
echo $result
}
number=5
result=$(square $number)
echo "Quadrado de $number e $result"Funcao com Multiplos Argumentos
#!/bin/bash
file_info() {
local filepath=$1
if [ -f "$filepath" ]; then
echo "Arquivo: $filepath"
echo "Tamanho: $(wc -c < "$filepath") bytes"
echo "Linhas: $(wc -l < "$filepath") linhas"
echo "Ultima modificacao: $(stat -c %y "$filepath")"
else
echo "Arquivo $filepath nao existe"
return 1
fi
}
file_info "/etc/passwd"
file_info "nonexistent.txt"Funcoes Avancadas
Variaveis Locais vs Globais
#!/bin/bash
global_var="global"
demo_scope() {
local local_var="local"
global_var="global modificado"
echo "Dentro da funcao: local_var = $local_var"
echo "Dentro da funcao: global_var = $global_var"
}
echo "Antes da chamada: global_var = $global_var"
demo_scope
echo "Apos a chamada: global_var = $global_var"
echo "Fora da funcao: local_var = $local_var" # vazioFuncao Recursiva
#!/bin/bash
factorial() {
local n=$1
if [ $n -le 1 ]; then
echo 1
else
local prev=$(factorial $((n - 1)))
echo $((n * prev))
fi
}
for i in {1..5}; do
result=$(factorial $i)
echo "$i! = $result"
doneFuncao com Tratamento de Erros
#!/bin/bash
safe_mkdir() {
local dir_path=$1
if [ -z "$dir_path" ]; then
echo "Erro: caminho do diretorio nao especificado" >&2
return 1
fi
if [ -d "$dir_path" ]; then
echo "Diretorio $dir_path ja existe"
return 0
fi
if mkdir -p "$dir_path" 2>/dev/null; then
echo "Diretorio $dir_path criado"
return 0
else
echo "Erro: falha ao criar diretorio $dir_path" >&2
return 1
fi
}
safe_mkdir "/tmp/test_dir"
safe_mkdir "/root/forbidden" # exemplo de erro de permissaoArrays
Conclusao: "${arr[@]}" para expansao segura; declare -A para arrays associativos (Bash 4.0+).
Operacoes Basicas com Arrays
Criando Arrays e Acessando Elementos
#!/bin/bash
fruits=("apple" "banana" "orange" "grape")
numbers=(1 2 3 4 5)
echo "Primeira fruta: ${fruits[0]}"
echo "Terceiro numero: ${numbers[2]}"
echo "Todas as frutas: ${fruits[@]}"
echo "Numero de frutas: ${#fruits[@]}"Adicionando e Removendo Elementos
#!/bin/bash
colors=("red" "green" "blue")
echo "Array inicial: ${colors[@]}"
colors+=("yellow")
echo "Apos adicao: ${colors[@]}"
unset colors[1] # remover "green"
echo "Apos exclusao: ${colors[@]}"
echo "Indices: ${!colors[@]}"Arrays Associativos (Bash 4.0+)
#!/bin/bash
declare -A person
person["name"]="John Smith"
person["age"]="30"
person["city"]="New York"
echo "Nome: ${person["name"]}"
echo "Idade: ${person["age"]}"
echo "Todas as chaves: ${!person[@]}"Uso Pratico de Arrays
#!/bin/bash
log_patterns=("/var/log/*.log" "/tmp/*.log" "$HOME/*.log")
for pattern in "${log_patterns[@]}"; do
echo "Padrao: $pattern"
files=($pattern)
if [ ${#files[@]} -gt 0 ] && [ -f "${files[0]}" ]; then
for file in "${files[@]}"; do
if [ -f "$file" ]; then
size=$(wc -c < "$file")
echo " - $file ($size bytes)"
fi
done
else
echo " - Nenhum arquivo correspondente"
fi
doneOperacoes com Arquivos
Conclusao: IFS= read -r + redirecionamento, nao pipe -- variaveis do loop sobrevivem fora do loop.
Lendo e Escrevendo Arquivos
Varios Metodos de Leitura de Arquivo
#!/bin/bash
filename="data.txt"
# Metodo 1: usando while read
while IFS= read -r line || [ -n "$line" ]; do
echo "Linha: $line"
done < "$filename"
# Metodo 2: ler em array
mapfile -t lines < "$filename"
for i in "${!lines[@]}"; do
echo "Linha $((i+1)): ${lines[i]}"
doneEscrevendo em Arquivos
#!/bin/bash output_file="output.txt" echo "Novo conteudo do arquivo" > "$output_file" echo "Linha adicional 1" >> "$output_file" cat << EOF >> "$output_file" Texto multi-linha Linha 2 Linha 3 EOF cat "$output_file"
Processando Arquivos CSV
#!/bin/bash
csv_file="employees.csv"
cat << EOF > "$csv_file"
Nome,Idade,Departamento
Alice,30,Engenharia
Bob,25,Vendas
Carol,35,Gerencia
EOF
tail -n +2 "$csv_file" | while IFS=',' read -r name age dept; do
echo "Funcionario: $name (idade $age) - $dept"
doneExemplo Avancado de Operacao com Arquivos
#!/bin/bash
source_dir="/path/to/source"
backup_dir="/path/to/backup"
backup_files() {
local src="$1"
local dest="$2"
if [ ! -d "$src" ]; then
echo "Erro: diretorio de origem nao existe: $src"
return 1
fi
mkdir -p "$dest"
rsync -av --delete "$src/" "$dest/"
echo "Backup completo: $src -> $dest"
}
log_file="/tmp/backup.log"
{
echo "Backup iniciado: $(date)"
backup_files "$source_dir" "$backup_dir"
echo "Backup finalizado: $(date)"
} >> "$log_file" 2>&1Tratamento de Erros
Conclusao: Inicie scripts de producao com set -euo pipefail e trap em ERR para falhar rapido.
Melhores Praticas de Tratamento de Erros
Tratamento Estrito de Erros com Opcoes set
#!/bin/bash set -euo pipefail # set -e: sair imediatamente quando um comando falha # set -u: erro em variaveis indefinidas # set -o pipefail: erro se qualquer comando em um pipeline falhar trap 'echo "Erro ocorrido: linha $LINENO" >&2' ERR echo "Processamento normal 1"
Verificacao Manual de Erros
#!/bin/bash
process_file() {
local filename="$1"
if [ ! -f "$filename" ]; then
echo "Erro: arquivo '$filename' nao encontrado" >&2
return 1
fi
if [ ! -r "$filename" ]; then
echo "Erro: sem permissao de leitura para '$filename'" >&2
return 1
fi
local line_count
if ! line_count=$(wc -l < "$filename" 2>/dev/null); then
echo "Erro: falha ao contar linhas" >&2
return 1
fi
echo "Contagem de linhas de '$filename': $line_count"
return 0
}
if process_file "test.txt"; then
echo "Processamento concluido com sucesso"
else
echo "Processamento falhou"
exit 1
fiTratamento de Erros com Logging
#!/bin/bash
LOG_FILE="/tmp/script.log"
log() {
local level="$1"
shift
local message="$*"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] [$level] $message" | tee -a "$LOG_FILE"
}
handle_error() {
local exit_code=$?
local line_no=$1
log "ERROR" "Script encerrou com erro (codigo de saida: $exit_code, linha: $line_no)"
exit $exit_code
}
trap 'handle_error $LINENO' ERR
log "INFO" "Script iniciado"
log "INFO" "Script finalizado"Exemplos de Scripts do Mundo Real
Conclusao: Backup, monitor de log, organizador -- tres scripts sobre funcoes, arrays e erros.
Exemplo 1: Script de Backup do Sistema
#!/bin/bash
set -euo pipefail
BACKUP_ROOT="/backup"
LOG_FILE="/var/log/backup.log"
RETENTION_DAYS=7
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIRS=(
"/etc"
"/home"
"/var/www"
"/usr/local/bin"
)
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
cleanup() {
log "Removendo backups antigos"
find "$BACKUP_ROOT" -type f -name "backup_*.tar.gz" -mtime +$RETENTION_DAYS -delete
log "Backups antigos removidos"
}
main_backup() {
local backup_file="$BACKUP_ROOT/backup_$DATE.tar.gz"
log "Iniciando backup: $backup_file"
mkdir -p "$BACKUP_ROOT"
if tar -czf "$backup_file" "${BACKUP_DIRS[@]}" 2>/dev/null; then
local size=$(du -h "$backup_file" | cut -f1)
log "Backup bem-sucedido: $backup_file (tamanho: $size)"
else
log "Erro: falha na criacao do backup"
exit 1
fi
}
if [ "$(id -u)" -ne 0 ]; then
echo "Este script deve ser executado como root"
exit 1
fi
log "Backup do sistema iniciado"
main_backup
cleanup
log "Backup do sistema concluido"Exemplo 2: Script de Monitoramento de Logs
#!/bin/bash
set -euo pipefail
LOG_FILE="/var/log/syslog"
ALERT_PATTERNS=("ERROR" "CRITICAL" "FAILED")
CHECK_INTERVAL=10
LAST_CHECK_FILE="$HOME/.log_monitor_last_check" # diretorio home e mais seguro que caminho previsivel em /tmp
send_alert() {
local message="$1"
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
echo "[$timestamp] ALERTA: $message" >> "/var/log/alerts.log"
logger "LOG_MONITOR_ALERT: $message"
}
monitor_logs() {
local last_position=0
if [ -f "$LAST_CHECK_FILE" ]; then
last_position=$(cat "$LAST_CHECK_FILE")
fi
local current_size=$(wc -c < "$LOG_FILE")
if [ "$current_size" -gt "$last_position" ]; then
local new_lines=$(tail -c +$((last_position + 1)) "$LOG_FILE")
for pattern in "${ALERT_PATTERNS[@]}"; do
if echo "$new_lines" | grep -q "$pattern"; then
local matches=$(echo "$new_lines" | grep "$pattern")
send_alert "Padrao detectado '$pattern': $matches"
fi
done
echo "$current_size" > "$LAST_CHECK_FILE"
fi
}
echo "Iniciando monitor de log: $LOG_FILE"
while true; do
monitor_logs
sleep "$CHECK_INTERVAL"
doneExemplo 3: Script Organizador de Arquivos
#!/bin/bash
set -euo pipefail
SOURCE_DIR="$HOME/Downloads"
ORGANIZE_ROOT="$HOME/Organized"
LOG_FILE="/tmp/file_organizer.log"
declare -A FILE_TYPES=(
["pdf"]="Documents/PDF"
["doc,docx"]="Documents/Word"
["jpg,jpeg,png,gif"]="Images"
["mp4,avi,mov"]="Videos"
["zip,rar,7z,tar,gz"]="Archives"
)
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
get_file_type() {
local filename="$1"
local extension="${filename##*.}"
extension=$(echo "$extension" | tr '[:upper:]' '[:lower:]')
for types in "${!FILE_TYPES[@]}"; do
if [[ ",$types," == *",$extension,"* ]]; then
echo "${FILE_TYPES[$types]}"
return 0
fi
done
echo "Others"
}
organize_files() {
log "Iniciando organizacao de arquivos: $SOURCE_DIR"
if [ ! -d "$SOURCE_DIR" ]; then
log "Erro: diretorio de origem nao existe: $SOURCE_DIR"
exit 1
fi
local file_count=0
while IFS= read -r -d '' file; do
if [ -f "$file" ]; then
local file_type=$(get_file_type "$(basename "$file")")
local dest_dir="$ORGANIZE_ROOT/$file_type"
mkdir -p "$dest_dir"
mv "$file" "$dest_dir/"
log "Movido: $(basename "$file") -> $file_type/"
file_count=$((file_count + 1))
fi
done < <(find "$SOURCE_DIR" -maxdepth 1 -type f -print0)
log "Organizacao completa: $file_count arquivos processados"
}
organize_filesErros Comuns e Armadilhas
Conclusao: Cinco armadilhas -- expansao, escopo, subshell, erros, seguranca -- cada uma evitavel.
Erro 1: Uso Incorreto de Arrays
NG (dividido por espacos)
files=("file1.txt" "file 2.txt" "file3.txt")
for file in $files; do # dividido por espacos
echo $file
doneOK (expansao correta)
files=("file1.txt" "file 2.txt" "file3.txt")
for file in "${files[@]}"; do # expandir como array
echo "$file"
doneUse "${array[@]}" para expandir o array completo com seguranca.
Erro 2: Mal-entendido do Escopo de Funcoes
NG (variavel global modificada sem intencao)
counter=0
increment() {
counter=$((counter + 1)) # modifica variavel global
local result=$counter
echo $result
}
increment
echo "Contador global: $counter" # mudanca nao intencionalOK (gerenciamento adequado de escopo)
global_counter=0
increment() {
local local_counter=$1
local_counter=$((local_counter + 1))
echo $local_counter
}
result=$(increment $global_counter)
global_counter=$resultErro 3: Armadilha de Modificacao de Variavel em Pipeline
NG (mudancas de variavel nao refletidas)
count=0
cat file.txt | while IFS= read -r line; do
count=$((count + 1))
done
echo "Linhas: $count" # permanece 0 (executa em subshell)OK (usar redirecionamento)
count=0
while IFS= read -r line; do
count=$((count + 1))
done < file.txt
echo "Linhas: $count" # contado corretamente
# ou
count=$(wc -l < file.txt)Erro 4: Tratamento de Erros Ausente
NG (erros em cascata)
cp source.txt backup.txt rm source.txt # deleta mesmo se a copia falhou curl -o data.json http://api.example.com/data process_data data.json # continua mesmo se o download falhou
OK (tratamento robusto de erros)
set -euo pipefail
if cp source.txt backup.txt; then
echo "Backup bem-sucedido"
rm source.txt
else
echo "Erro: backup falhou" >&2
exit 1
fiErro 5: Implementacao Sem Consciencia de Seguranca
NG (problemas de seguranca)
read -p "Digite o comando: " user_command eval $user_command # execucao arbitraria de comando (perigoso) temp_file="/tmp/script_data.txt" # nome de arquivo previsivel echo "dados sensiveis" > $temp_file mysql -u user -p'password123' -e "SELECT * FROM users" # senha nos logs
OK (implementacao segura)
# validar entrada
read -p "Digite o nome do arquivo: " filename
if [[ "$filename" =~ ^[a-zA-Z0-9._-]+$ ]]; then
echo "Processando: $filename"
else
echo "Erro: nome de arquivo invalido" >&2
exit 1
fi
# criacao segura de arquivo temporario
temp_file=$(mktemp)
trap 'rm -f "$temp_file"' EXIT
# ler senha do arquivo de configuracao (proteger com: chmod 600 ~/.mysql_config)
if [ -f ~/.mysql_config ]; then
source ~/.mysql_config
mysql -u "$DB_USER" -p"$DB_PASS" -e "SELECT * FROM users"
fiErro 6: Implementacao Sem Consciencia de Desempenho
NG (processamento ineficiente)
for file in /path/to/large/directory/*; do
wc -l "$file" # cria um novo processo por arquivo
done
for i in {1..1000}; do
current_time=$(date +%s) # executa o comando date em cada iteracao
echo "Processando $i em $current_time"
doneOK (implementacao eficiente)
# processamento em lote para eficiencia
find /path/to/large/directory -name "*" -type f -exec wc -l {} +
# buscar uma vez, reutilizar
start_time=$(date +%s)
for i in {1..1000}; do
current_time=$((start_time + i))
echo "Processando $i em $current_time"
doneMelhores Praticas
Conclusao: Bash em producao: nomes claros, entrada validada, mktemp, set -x e ShellCheck.
Padroes de Codificacao
- Use nomes de variaveis significativos
- Mantenha funcoes focadas em uma unica responsabilidade
- Comente para esclarecer intencao, nao mecanica
- Use indentacao consistente (2 espacos recomendado)
Seguranca
- Sempre valide a entrada do usuario
- Use
mktemppara criacao segura de arquivos temporarios - Defina permissoes minimas necessarias
- Nunca codifique informacoes sensiveis diretamente no codigo
Depuracao
- Habilite o modo de depuracao com
set -x - Implemente logging apropriado
- Desenvolva e teste incrementalmente
- Use ferramentas de analise estatica como ShellCheck
Resumo
Dominar tecnicas praticas de shell scripting permite automacao eficiente e confiavel.
- Funcoes melhoram a reutilizacao e manutencao do codigo
- Arrays simplificam o processamento complexo de dados
- Tratamento adequado de erros cria scripts robustos
- Exemplos do mundo real fornecem padroes que voce pode adaptar para producao