Shell Scripting Pratico: Funcoes, Arrays e Tecnicas de Operacao com Arquivos

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 pipefail e trap constroem 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"  # vazio

Funcao 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"
done

Funcao 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 permissao

Arrays

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
done

Operacoes 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]}"
done

Escrevendo 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"
done

Exemplo 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>&1

Tratamento 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
fi

Tratamento 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"
done

Exemplo 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_files

Erros 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
done

OK (expansao correta)

files=("file1.txt" "file 2.txt" "file3.txt")
for file in "${files[@]}"; do    # expandir como array
    echo "$file"
done

Use "${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 intencional

OK (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=$result

Erro 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
fi

Erro 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"
fi

Erro 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"
done

OK (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"
done

Melhores 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 mktemp para 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

Proximas Leituras