0Pricing
DevOps Bootcamp · Aula

Projeto de funções com escopo local e códigos de retorno

Escreva funções que usem variáveis locais, status de saída e valores de retorno baseados em printf em vez de globais frágeis.

Projeto de funções com escopo local e códigos de retorno é uma aula grátis de DevOps Bootcamp no CoddyKit. Esta é a aula 1 de 4. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de DevOps Bootcamp, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de DevOps Bootcamp inclui 4 aulas no total.

Por que o escopo das funções é importante

No Bash, as variáveis são globais por padrão. Uma variável definida dentro de uma função escapa para o escopo de quem a chamou, a menos que você a declare explicitamente como local. Essa é uma fonte comum de erros sutis em scripts do shell.

  • Funções sem local podem substituir silenciosamente variáveis de quem as chamou.
  • As variáveis local são destruídas quando a função retorna.
  • Limites de escopo bem definidos tornam as funções reutilizáveis e testáveis de forma isolada.

Nesta lição, você aprenderá a escrever funções autocontidas: elas usam variáveis local, comunicam resultados por meio de códigos de saída e printf e nunca dependem de um estado global implícito.

O problema dos vazamentos globais

Este é um exemplo concreto de vazamento de variável global. A função set_name define uma variável chamada result, sobrescrevendo silenciosamente a própria variável result do chamador.

Execute este script e observe a saída inesperada — a variável result do chamador desaparece depois da chamada da função.

#!/usr/bin/env bash

set_name() {
    result="Alice"   # No 'local' — this is GLOBAL
}

result="important data"
echo "Before: $result"

set_name

echo "After:  $result"   # Prints 'Alice', not 'important data'

Declarando variáveis locais com 'local'

O comando interno local restringe o escopo de uma variável à função envolvente e a quaisquer funções que ela chame. Fora da função, a variável fica indefinida ou mantém seu valor anterior.

  • local varname — declara sem atribuir.
  • local varname="value" — declara e atribui em uma única etapa.
  • local -i count=0 — declara uma variável local de tipo inteiro.
  • local -r PI=3.14159 — declara uma constante local somente para leitura.

Boa prática: declare como local todas as variáveis dentro de uma função, a menos que precise intencionalmente que sejam globais.

#!/usr/bin/env bash

greet() {
    local name="$1"          # local — safe
    local greeting="Hello, ${name}!"
    echo "$greeting"
}  # 'name' and 'greeting' vanish here

name="global value"
greet "Bob"
echo "name is still: $name"   # Prints 'global value'

Códigos de saída como valores de retorno

As funções do Bash não podem usar return para retornar strings — return apenas define um status de saída inteiro (0–255). Por convenção:

  • return 0 — sucesso
  • return 1 (ou qualquer valor diferente de zero) — falha

O chamador lê o status de saída por meio de $? imediatamente após a chamada ou usa a função diretamente em uma condição if. Os códigos de saída são a forma idiomática de sinalizar sucesso ou falha de uma função.

#!/usr/bin/env bash

is_even() {
    local -i n="$1"
    (( n % 2 == 0 ))   # arithmetic command: exits 0 if true, 1 if false
}

for num in 2 3 7 10; do
    if is_even "$num"; then
        echo "$num is even"
    else
        echo "$num is odd"
    fi
done

Comunicando resultados de strings por meio de printf

Quando uma função precisa retornar um resultado de string, o padrão é imprimir na saída padrão e capturar o valor com a substituição de comando $(). É preferível usar printf em vez de echo porque:

  • printf não acrescenta uma nova linha final por padrão (a menos que você inclua \n).
  • O comportamento de printf é consistente e definido pelo POSIX; echo varia entre os interpretadores de comandos.
  • A substituição de comando remove novas linhas finais, portanto printf '%s' "$value" é preciso.
#!/usr/bin/env bash

to_uppercase() {
    local input="$1"
    printf '%s' "${input^^}"   # Bash 4+ parameter expansion
}

word="hello"
upper=$(to_uppercase "$word")
echo "Original: $word"
echo "Upper:    $upper"

Combinando códigos de saída e saída padrão

Uma função bem projetada pode tanto imprimir um resultado (em caso de sucesso) quanto sinalizar uma falha (por meio do código de saída) ao mesmo tempo. O chamador decide o que fazer com base no código de saída antes de confiar na saída.

O padrão abaixo é usado amplamente em bibliotecas Bash reais:

  • Em caso de sucesso: use printf para imprimir o resultado e return 0.
  • Em caso de falha: escreva um diagnóstico na saída de erro (não na saída padrão) e use return 1.
  • Escrever erros na saída de erro mantém a saída padrão limpa para o encadeamento.
#!/usr/bin/env bash

divide() {
    local -i numerator="$1"
    local -i denominator="$2"

    if (( denominator == 0 )); then
        printf 'Error: division by zero\n' >&2
        return 1
    fi

    printf '%d' $(( numerator / denominator ))
    return 0
}

if result=$(divide 20 4); then
    echo "20 / 4 = $result"
else
    echo "Division failed."
fi

if result=$(divide 10 0); then
    echo "10 / 0 = $result"
else
    echo "Division failed (caught the error)."
fi

Usando 'local' para proteger funções recursivas

Recursão é uma das demonstrações mais claras de por que local é essencial. Cada chamada recursiva recebe uma cópia independente de cada variável local na pilha de chamadas. Sem local, cada chamada sobrescreveria a mesma variável global e produziria resultados incorretos.

A função fatorial abaixo é segura porque n e sub são locais a cada quadro da pilha.

#!/usr/bin/env bash

factorial() {
    local -i n="$1"
    local -i sub

    if (( n <= 1 )); then
        printf '1'
        return 0
    fi

    sub=$(factorial $(( n - 1 )))
    printf '%d' $(( n * sub ))
}

for i in 1 2 3 4 5 6; do
    echo "${i}! = $(factorial $i)"
done

Evitando a armadilha do subshell com local -n (referência de nome)

A substituição de comando $() é executada em um subshell. Qualquer atribuição de variável feita dentro dele fica invisível para o interpretador de comandos pai. Quando precisar que uma função escreva em uma variável fornecida pelo chamador sem usar um subshell, use uma referência de nome (local -n), disponível no Bash 4.3+.

  • local -n ref="$1" transforma ref em um alias da variável cujo nome está armazenado em $1.
  • Atribuir um valor a ref dentro da função altera diretamente a variável do chamador.
  • Isso evita um subshell e, ao mesmo tempo, mantém os detalhes da implementação locais.
#!/usr/bin/env bash

# Fills caller's array by reference — no subshell needed
read_csv_line() {
    local -n _out="$1"    # nameref to caller's variable
    local line="$2"
    local IFS=','
    read -ra _out <<< "$line"
}

declare -a fields
read_csv_line fields "alice,30,engineer"

echo "Name:  ${fields[0]}"
echo "Age:   ${fields[1]}"
echo "Role:  ${fields[2]}"

Criando uma pequena biblioteca de funções

Projetos Bash reais dividem funções reutilizáveis em arquivos de biblioteca, carregados por scripts com source (ou com o operador ponto .). Boas regras de projeto para bibliotecas:

  • Toda variável dentro de uma função de biblioteca deve ser local.
  • Funções de biblioteca nunca usam exit — elas usam return, para que o chamador continue em execução.
  • Use um prefixo de espaço de nomes consistente (por exemplo, str_, log_) para evitar conflitos de nomes.
  • Evite o carregamento duplicado usando uma variável sentinela.

Abaixo está uma biblioteca mínima de utilitários de strings que segue essas convenções.

#!/usr/bin/env bash
# lib/str.sh  — string utility library

[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1

str_trim() {
    local str="$1"
    str="${str#"${str%%[![:space:]]*}"}"
    str="${str%"${str##*[![:space:]]}"}" 
    printf '%s' "$str"
}

str_repeat() {
    local -i times="$2"
    local char="$1"
    local -i i
    for (( i = 0; i < times; i++ )); do
        printf '%s' "$char"
    done
}

str_contains() {
    local haystack="$1"
    local needle="$2"
    [[ "$haystack" == *"$needle"* ]]
}

# --- self-test when executed directly ---
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
    trimmed=$(str_trim "   hello world   ")
    echo "Trimmed: '${trimmed}'"
    str_repeat '-' 20; echo
    if str_contains "bash scripting" "script"; then
        echo "Contains: yes"
    fi
fi

Validando argumentos dentro das funções

As funções que recebem argumentos devem validá-los cedo e retornar um código de saída específico quando a entrada for inválida. Esse padrão é chamado de cláusula de guarda — falhe rapidamente e de forma clara.

  • Verifique a quantidade de argumentos com $#.
  • Valide os tipos ou formatos antes de realizar qualquer trabalho.
  • Imprima mensagens de diagnóstico somente na saída de erro, nunca na saída padrão.
  • Use códigos de retorno diferentes de zero (por exemplo, 1 = argumentos incorretos, 2 = arquivo não encontrado) para que os chamadores possam reagir de forma diferente a cada modo de falha.
#!/usr/bin/env bash

file_line_count() {
    if (( $# != 1 )); then
        printf 'Usage: file_line_count <file>\n' >&2
        return 1
    fi

    local file="$1"

    if [[ ! -f "$file" ]]; then
        printf 'Error: not a file: %s\n' "$file" >&2
        return 2
    fi

    if [[ ! -r "$file" ]]; then
        printf 'Error: cannot read: %s\n' "$file" >&2
        return 3
    fi

    local -i count
    count=$(wc -l < "$file")
    printf '%d' "$count"
    return 0
}

# Test with /etc/hosts (exists on every Linux/macOS system)
if lines=$(file_line_count /etc/hosts); then
    echo "/etc/hosts has $lines lines"
else
    echo "Failed with exit code: $?"
fi

Juntando tudo: um exemplo do mundo real

Aqui está um script completo e independente que demonstra todos os conceitos desta lição funcionando em conjunto:

  • Variáveis local em todas as funções.
  • Códigos de saída para sinalizar sucesso ou falha.
  • printf para comunicar resultados de strings.
  • Erros escritos na saída de erro e resultados na saída padrão.
  • Cláusulas de guarda para validação de argumentos.

Observe o fluxo: parse_version extrai os dados, version_ge faz a comparação e main usa ambos de forma organizada.

#!/usr/bin/env bash

# Parse a semver string into components via nameref
parse_version() {
    local -n _major="$2" _minor="$3" _patch="$4"
    local version="$1"
    local IFS='.'
    local -a parts
    read -ra parts <<< "$version"
    _major="${parts[0]:-0}"
    _minor="${parts[1]:-0}"
    _patch="${parts[2]:-0}"
}

# Return 0 if version $1 >= version $2
version_ge() {
    local -i maj_a min_a pat_a
    local -i maj_b min_b pat_b
    parse_version "$1" maj_a min_a pat_a
    parse_version "$2" maj_b min_b pat_b

    if   (( maj_a != maj_b )); then (( maj_a > maj_b ))
    elif (( min_a != min_b )); then (( min_a > min_b ))
    else                             (( pat_a >= pat_b ))
    fi
}

require_bash_version() {
    local required="$1"
    local actual="${BASH_VERSION%%(*}"
    if version_ge "$actual" "$required"; then
        printf 'Bash %s satisfies >= %s\n' "$actual" "$required"
        return 0
    else
        printf 'Error: need Bash >= %s, got %s\n' "$required" "$actual" >&2
        return 1
    fi
}

main() {
    require_bash_version "4.3" || return 1
    require_bash_version "99.0" || true   # demonstrates failure path
}

main

Verificação de conhecimentos: variáveis locais e valores de retorno

Considere a seguinte função Bash. Qual é a forma correta de capturar seu resultado de string no chamador e qual afirmação sobre a variável tmp é verdadeira?

transform() {
    local tmp="${1,,}"   # lowercase
    printf '%s' "$tmp"
    return 0
}

Recapitulação: funções com escopo local e códigos de retorno

Nesta lição, você aprendeu a escrever funções Bash organizadas, combináveis e seguras:

  • Use sempre local para as variáveis dentro das funções, a fim de evitar poluir o escopo do chamador.
  • Use códigos de saída (return 0/1/N) para sinalizar sucesso ou falha — eles se integram naturalmente a if, && e ||.
  • Use printf na saída padrão para comunicar resultados de strings; capture-os com $() no chamador.
  • Escreva os erros na saída de erro (>&2) para manter a saída padrão limpa para o fluxo de dados e o encadeamento.
  • Use local -n (referência de nome) quando precisar escrever em uma variável fornecida pelo chamador sem o custo de um subshell.
  • Cláusulas de guarda (validar os argumentos cedo e retornar imediatamente quando a entrada for inválida) tornam as funções robustas e autoexplicativas.
  • Arquivos de biblioteca devem ser carregados, usar prefixos de espaço de nomes, nunca chamar exit e evitar o carregamento duplicado.

Dominar esses padrões é o que diferencia scripts frágeis e feitos para uma única ocasião de bases de código Bash profissionais e fáceis de manter.

Perguntas Frequentes

A aula “Projeto de funções com escopo local e códigos de retorno” é grátis?

Sim — o texto completo de “Projeto de funções com escopo local e códigos de retorno” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de DevOps Bootcamp, atualize para CoddyKit PRO. O curso de DevOps Bootcamp inclui 4 aulas no total.

O que vou aprender em “Projeto de funções com escopo local e códigos de retorno”?

Escreva funções que usem variáveis locais, status de saída e valores de retorno baseados em printf em vez de globais frágeis. Você pratica DevOps Bootcamp com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.

Preciso ter experiência prévia para começar DevOps Bootcamp?

Nenhuma experiência prévia é necessária. DevOps Bootcamp no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 1 de 4.

Quanto tempo leva a aula “Projeto de funções com escopo local e códigos de retorno”?

A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.

Posso escrever e executar código nesta aula de DevOps Bootcamp?

Sim. Cada aula de DevOps Bootcamp inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.

Todas as aulas deste curso

  1. Projeto de funções com escopo local e códigos de retorno
  2. Construção e carregamento de bibliotecas Bash reutilizáveis
  3. Análise de opções e argumentos com getopts
  4. Passagem de matrizes e mapas associativos entre funções
← Voltar para DevOps Bootcamp