Funções em Bash: definição e escopo

1. Introdução às Funções no Bash

Funções em Bash são blocos de código nomeados que podem ser invocados múltiplas vezes durante a execução de um script. Elas promovem modularidade, reuso de código e organização, permitindo dividir problemas complexos em partes menores e mais gerenciáveis.

Diferentemente de linguagens tradicionais como C ou Python, funções em Bash não possuem tipagem explícita, não retornam valores diretamente (apenas códigos de saída numéricos) e operam em um contexto de shell que inclui variáveis de ambiente, redirecionamentos e pipes. A compreensão do escopo de variáveis é fundamental para evitar efeitos colaterais indesejados.

2. Sintaxe de Definição de Funções

Existem duas formas principais de definir funções em Bash:

Forma clássica (Bash-specific):

function saudacao {
    echo "Olá, $1!"
}

Forma POSIX (portável):

saudacao() {
    echo "Olá, $1!"
}

Ambas são equivalentes, mas a forma POSIX é recomendada para portabilidade entre shells. Regras de nomenclatura: use letras, números e underscores, começando com letra ou underscore. Evite nomes de comandos do sistema (ls, cd, echo) para não sobrescrevê-los acidentalmente.

3. Chamando Funções e Passagem de Argumentos

A chamada é feita simplesmente pelo nome da função, seguida dos argumentos separados por espaço:

#!/bin/bash

soma() {
    local resultado=$(( $1 + $2 ))
    echo "A soma de $1 e $2 é $resultado"
}

soma 10 20

Dentro da função, os argumentos são acessados por:
- $1, $2, ... — argumentos posicionais
- $@ — todos os argumentos como lista
- $# — número total de argumentos

Exemplo prático:

#!/bin/bash

calcula_media() {
    local soma=0
    local count=0
    for num in "$@"; do
        soma=$((soma + num))
        count=$((count + 1))
    done
    echo "Média: $((soma / count))"
}

calcula_media 8 9 7 10

4. Escopo de Variáveis: Global vs Local

Por padrão, todas as variáveis em Bash são globais — mesmo as declaradas dentro de funções. Isso pode causar bugs difíceis de rastrear.

#!/bin/bash

teste_escopo() {
    x=10
    echo "Dentro da função: x = $x"
}

x=5
teste_escopo
echo "Fora da função: x = $x"  # Imprime 10 (foi sobrescrito!)

A palavra-chave local cria variáveis com escopo restrito à função atual e suas subfunções:

#!/bin/bash

teste_local() {
    local y=10
    echo "Dentro: y = $y"
}

y=5
teste_local
echo "Fora: y = $y"  # Imprime 5 (inalterado)

Impacto de não usar local: sobrescrita acidental de variáveis, comportamento imprevisível em scripts complexos, dificuldade de manutenção. A regra de ouro é: sempre declare variáveis internas com local.

5. Retorno de Valores em Funções

Funções em Bash não retornam valores como em outras linguagens. Existem dois mecanismos:

Código de saída com return: retorna um número inteiro entre 0 e 255. Captura-se com $?:

#!/bin/bash

eh_par() {
    if (( $1 % 2 == 0 )); then
        return 0  # sucesso (verdadeiro)
    else
        return 1  # falha (falso)
    fi
}

eh_par 4
echo "Código de retorno: $?"  # 0

eh_par 7
echo "Código de retorno: $?"  # 1

Retornando strings via echo: mais flexível, captura-se com substituição de comando:

#!/bin/bash

saudacao_personalizada() {
    local nome=$1
    local hora=$(date +%H)
    if [ "$hora" -lt 12 ]; then
        echo "Bom dia, $nome!"
    else
        echo "Boa tarde, $nome!"
    fi
}

mensagem=$(saudacao_personalizada "Maria")
echo "$mensagem"

Limitação importante: funções não podem retornar arrays diretamente. Para contornar, use echo com delimitadores ou passe o nome da variável como argumento (dica: veja artigo sobre arrays em Bash).

6. Funções Recursivas e Cuidados com Subshells

Funções recursivas são possíveis, mas exigem cuidado:

#!/bin/bash

fatorial() {
    local n=$1
    if [ "$n" -le 1 ]; then
        echo 1
    else
        local anterior=$(fatorial $((n - 1)))
        echo $((n * anterior))
    fi
}

echo "Fatorial de 5: $(fatorial 5)"  # 120

Subshells: comandos entre parênteses ( ) criam um subshell, onde variáveis locais do shell pai não são visíveis. Já chaves { } executam no shell atual:

#!/bin/bash

teste_shell() {
    local x=10
    (echo "Subshell: x=$x"; x=20)
    echo "Após subshell: x=$x"  # Ainda 10
}

teste_shell

Boas práticas: evite recursão profunda (risco de estouro de pilha), sempre use condição de parada clara, prefira loops quando possível.

7. Funções como Comandos e Aliases

Exportando funções para subshells: use export -f nome_funcao:

#!/bin/bash

minha_func() {
    echo "Executando em subshell"
}

export -f minha_func
bash -c 'minha_func'  # Funciona

Funções no .bashrc: permanecem disponíveis em todo shell interativo. São ideais para comandos personalizados frequentes.

Diferença entre função e alias:
- Alias: simples substituição de texto, não aceita argumentos posicionais facilmente
- Função: estrutura completa, aceita argumentos, variáveis locais, lógica condicional

Use alias para atalhos simples (alias ll='ls -la') e funções para lógica mais complexa.

8. Boas Práticas e Depuração

Documentação inline:

# Função: valida_email
# Descrição: Verifica se o argumento é um email válido
# Uso: valida_email "usuario@dominio.com"
# Retorno: 0 se válido, 1 caso contrário
valida_email() {
    local email=$1
    if [[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
        return 0
    else
        return 1
    fi
}

Depuração com set -x e set -e:
- set -x: exibe cada comando antes de executar (rastreio)
- set -e: interrompe o script ao primeiro erro

#!/bin/bash
set -e  # Para em caso de erro

minha_funcao() {
    set -x  # Ativa rastreio apenas nesta função
    local a=$1
    local b=$2
    echo $((a / b))
    set +x  # Desativa rastreio
}

Organização: agrupe funções no início do script ou em arquivos separados carregados com source:

# No arquivo utils.sh
soma() { echo $(( $1 + $2 )); }
multiplica() { echo $(( $1 * $2 )); }

# No script principal
source utils.sh
resultado=$(soma 5 3)

Funções bem escritas transformam scripts Bash de sequências frágeis de comandos em programas modulares, testáveis e reutilizáveis. Dominar definição, escopo e retorno é essencial para qualquer profissional que trabalhe com automação em Unix/Linux.

Referências