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 Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Linux Command Line & Bash Scripting Mastery 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
localpodem substituir silenciosamente variáveis de quem as chamou. - As variáveis
localsã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— sucessoreturn 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
doneComunicando 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:
printfnão acrescenta uma nova linha final por padrão (a menos que você inclua\n).- O comportamento de
printfé consistente e definido pelo POSIX;echovaria 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
printfpara imprimir o resultado ereturn 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)."
fiUsando '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)"
doneEvitando 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"transformarefem um alias da variável cujo nome está armazenado em$1.- Atribuir um valor a
refdentro 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 usamreturn, 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
fiValidando 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: $?"
fiJuntando 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
localem todas as funções. - Códigos de saída para sinalizar sucesso ou falha.
printfpara 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
}
mainVerificaçã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
localpara 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 aif,&&e||. - Use
printfna 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
exite 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 Linux Command Line & Bash Scripting Mastery, atualize para CoddyKit PRO. O curso de Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?
Nenhuma experiência prévia é necessária. Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?
Sim. Cada aula de Linux Command Line & Bash Scripting Mastery 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
- Projeto de funções com escopo local e códigos de retorno
- Construção e carregamento de bibliotecas Bash reutilizáveis
- Análise de opções e argumentos com getopts
- Passagem de matrizes e mapas associativos entre funções