Progettazione di funzioni con ambito locale e codici di ritorno
Scriva funzioni che usano variabili locali, stati di uscita e valori di ritorno basati su printf invece di globali fragili.
Progettazione di funzioni con ambito locale e codici di ritorno è una lezione Linux Command Line & Bash Scripting Mastery gratuita su CoddyKit. Questa è la lezione 1 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Linux Command Line & Bash Scripting Mastery, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Linux Command Line & Bash Scripting Mastery include 4 lezioni in totale.
Perché l'ambito delle funzioni è importante
In Bash, le variabili sono globali per impostazione predefinita. Una variabile impostata all'interno di una funzione si propaga all'ambito del chiamante, a meno che non venga dichiarata esplicitamente local. Questa è una fonte comune di bug difficili da individuare negli script della shell.
- Le funzioni senza
localpossono sovrascrivere silenziosamente le variabili del chiamante. - Le variabili
localvengono eliminate quando la funzione restituisce il controllo. - Confini chiari tra gli ambiti rendono le funzioni riutilizzabili e testabili in isolamento.
Questa lezione insegna a scrivere funzioni autonome: usano variabili local, comunicano i risultati tramite codici di uscita e printf e non si affidano mai a uno stato globale implicito.
Il problema delle perdite globali
Ecco un esempio concreto di perdita di una variabile globale. La funzione set_name imposta una variabile chiamata result, sovrascrivendo silenziosamente la variabile result del chiamante.
Esegua questo script e osservi l'output imprevisto: la variabile result del chiamante scompare dopo la chiamata alla funzione.
#!/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'Dichiarare variabili locali con «local»
Il comando incorporato local limita l'ambito di una variabile alla funzione che la contiene e a tutte le funzioni da essa chiamate. Al di fuori della funzione, la variabile non è impostata oppure conserva il valore precedente.
local varname— dichiara la variabile senza assegnarle un valore.local varname="value"— dichiara la variabile e le assegna un valore in un unico passaggio.local -i count=0— dichiara una variabile locale di tipo intero.local -r PI=3.14159— dichiara una costante locale di sola lettura.
Buona pratica: dichiari ogni variabile all'interno di una funzione come local, a meno che non sia necessario intenzionalmente renderla globale.
#!/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'Codici di uscita come valori restituiti
Le funzioni Bash non possono usare return per restituire stringhe: return imposta esclusivamente uno stato di uscita intero (0–255). Per convenzione:
return 0— operazione riuscitareturn 1(o qualsiasi valore diverso da zero) — errore
Il chiamante legge lo stato di uscita tramite $? subito dopo la chiamata oppure usa direttamente la funzione nella condizione di un if. I codici di uscita sono il modo idiomatico per segnalare il successo o l'errore di una funzione.
#!/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
doneComunicare risultati stringa tramite printf
Quando una funzione deve restituire un risultato stringa, il modello standard consiste nello scrivere su stdout e acquisire il risultato con la sostituzione di comando $(). È preferibile usare printf invece di echo perché:
printfnon aggiunge per impostazione predefinita un carattere di nuova riga finale (a meno che non si includa\n).- Il comportamento di
printfè coerente ed è definito da POSIX;echovaria tra le shell. - La sostituzione di comando rimuove i caratteri di nuova riga finali, quindi
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"Combinare codici di uscita e output su stdout
Una funzione ben progettata può sia stampare un risultato (in caso di successo) sia segnalare un errore (tramite il codice di uscita) contemporaneamente. Il chiamante decide come procedere in base al codice di uscita, prima di considerare attendibile l'output.
Il modello seguente è ampiamente utilizzato nelle librerie Bash reali:
- In caso di successo: usare
printfper il risultato ereturn 0. - In caso di errore: scrivere una diagnostica su stderr (non su stdout) e usare
return 1. - Scrivere gli errori su stderr mantiene stdout pulito per il piping.
#!/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)."
fiUsare «local» per proteggere le funzioni ricorsive
La ricorsione è una delle dimostrazioni più chiare del motivo per cui local è essenziale. Ogni chiamata ricorsiva riceve una copia indipendente di ogni variabile local nello stack delle chiamate. Senza local, ogni chiamata sovrascriverebbe la stessa variabile globale, producendo risultati errati.
La funzione fattoriale seguente è sicura perché n e sub sono locali a ciascun frame dello stack.
#!/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)"
doneEvitare la trappola della subshell con local -n (Nameref)
La sostituzione di comando $() viene eseguita in una subshell. Qualsiasi assegnazione di variabile al suo interno è invisibile alla shell padre. Quando è necessario che una funzione scriva in una variabile fornita dal chiamante senza usare una subshell, si utilizzi un nameref (local -n), disponibile in Bash 4.3 e versioni successive.
local -n ref="$1"renderefun alias della variabile il cui nome è memorizzato in$1.- L'assegnazione a
refall'interno della funzione modifica direttamente la variabile del chiamante. - In questo modo si evita una subshell, mantenendo comunque locali i dettagli dell'implementazione.
#!/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]}"Creare una piccola libreria di funzioni
Nei progetti Bash reali, le funzioni riutilizzabili vengono suddivise in file di libreria caricati dagli script con source (o con l'operatore punto .). Regole per una buona progettazione delle librerie:
- Ogni variabile all'interno di una funzione di libreria deve essere
local. - Le funzioni di libreria non usano mai
exit: usanoreturn, così il chiamante rimane in esecuzione. - Utilizzi un prefisso di namespace coerente (ad esempio
str_,log_) per evitare conflitti tra nomi. - Prevenga il caricamento multiplo usando una variabile sentinella.
Di seguito è riportata una libreria minima di utilità per le stringhe che segue queste convenzioni.
#!/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
fiConvalidare gli argomenti all'interno delle funzioni
Le funzioni che ricevono argomenti dovrebbero convalidarli subito e restituire un codice di uscita specifico in caso di input non valido. Questo è il cosiddetto modello della clausola di guardia: interrompere rapidamente l'esecuzione e segnalare chiaramente il problema.
- Controlli il numero di argomenti con
$#. - Convalidi i tipi o i formati prima di eseguire qualsiasi operazione.
- Stampi i messaggi diagnostici esclusivamente su stderr, mai su stdout.
- Utilizzi codici di ritorno distinti e diversi da zero (ad esempio 1 = argomenti errati, 2 = file non trovato), così i chiamanti possono reagire diversamente alle varie cause di errore.
#!/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: $?"
fiUnire tutti gli elementi: un esempio reale
Ecco uno script completo e autonomo che dimostra il funzionamento congiunto di tutti i concetti di questa lezione:
- Variabili
localin ogni funzione. - Codici di uscita per segnalare il successo o l'errore.
printfper comunicare risultati stringa.- Errori scritti su stderr e risultati su stdout.
- Clausole di guardia per la convalida degli argomenti.
Analizzi il flusso: parse_version estrae i dati, version_ge li confronta e main utilizza entrambe le funzioni in modo ordinato.
#!/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 delle conoscenze: variabili locali e valori restituiti
Consideri la seguente funzione Bash. Qual è il modo corretto di acquisire il suo risultato stringa nel chiamante e quale affermazione sulla variabile tmp è vera?
transform() {
local tmp="${1,,}" # lowercase
printf '%s' "$tmp"
return 0
}Riepilogo: funzioni con ambito locale e codici di ritorno
In questa lezione ha imparato a scrivere funzioni Bash ordinate, componibili e sicure:
- Utilizzi sempre
localper le variabili all'interno delle funzioni, così da evitare di inquinare l'ambito del chiamante. - Utilizzi i codici di uscita (
return 0/1/N) per segnalare il successo o l'errore: si integrano naturalmente conif,&&e||. - Utilizzi
printfsu stdout per comunicare risultati stringa; li acquisisca nel chiamante con$(). - Scriva gli errori su stderr (
>&2), così stdout rimane pulito per il flusso dei dati e il piping. - Utilizzi
local -n(nameref) quando deve scrivere in una variabile fornita dal chiamante senza il sovraccarico di una subshell. - Le clausole di guardia (convalidare subito gli argomenti e restituire immediatamente un valore in caso di input non valido) rendono le funzioni robuste e autoesplicative.
- I file di libreria devono essere caricati con
source, utilizzare prefissi di namespace, non chiamare maiexite prevenire il caricamento multiplo.
Padroneggiare questi modelli fa la differenza tra script fragili e realizzati una tantum e codebase Bash professionali e manutenibili.
Domande Frequenti
La lezione «Progettazione di funzioni con ambito locale e codici di ritorno» è gratuita?
Sì — il testo completo di «Progettazione di funzioni con ambito locale e codici di ritorno» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Linux Command Line & Bash Scripting Mastery, passa a CoddyKit PRO. Il corso Linux Command Line & Bash Scripting Mastery include 4 lezioni in totale.
Cosa imparerò in «Progettazione di funzioni con ambito locale e codici di ritorno»?
Scriva funzioni che usano variabili locali, stati di uscita e valori di ritorno basati su printf invece di globali fragili. Eserciti Linux Command Line & Bash Scripting Mastery con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.
Ho bisogno di esperienza per iniziare Linux Command Line & Bash Scripting Mastery?
Non è richiesta alcuna esperienza precedente. Linux Command Line & Bash Scripting Mastery su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 1 di 4.
Quanto tempo richiede la lezione «Progettazione di funzioni con ambito locale e codici di ritorno»?
La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.
Posso scrivere ed eseguire codice in questa lezione Linux Command Line & Bash Scripting Mastery?
Sì. Ogni lezione Linux Command Line & Bash Scripting Mastery include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.
Tutte le lezioni di questo corso
- Progettazione di funzioni con ambito locale e codici di ritorno
- Creazione e sourcing di librerie Bash riutilizzabili
- Analisi di flag e argomenti con getopts
- Passaggio di array e mappe associative tra funzioni