0Pricing
DevOps Bootcamp · Lección

Diseño de funciones con ámbito local y códigos de retorno

Escriba funciones que usen variables locales, estados de salida y valores de retorno basados en printf en lugar de variables globales frágiles.

Diseño de funciones con ámbito local y códigos de retorno es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de DevOps Bootcamp, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de DevOps Bootcamp incluye 4 lecciones en total.

Por qué importa el ámbito de las funciones

En Bash, las variables son globales de forma predeterminada. Una variable definida dentro de una función se filtra al ámbito de quien la llama, a menos que la declare explícitamente como local. Esta es una fuente habitual de errores sutiles en los scripts de shell.

  • Las funciones sin local pueden sobrescribir silenciosamente las variables de quien las llama.
  • Las variables local se destruyen cuando la función retorna.
  • Unos límites de ámbito claros hacen que las funciones sean reutilizables y comprobables de forma aislada.

En esta lección aprenderá a escribir funciones autónomas: utilizan variables local, comunican los resultados mediante códigos de salida y printf, y nunca dependen de un estado global implícito.

El problema de las fugas globales

Aquí tiene un ejemplo concreto de una fuga de una variable global. La función set_name establece una variable llamada result, que sobrescribe silenciosamente la propia variable result de quien la llama.

Ejecute este script y observe la salida inesperada: la variable result de quien llama desaparece después de la llamada a la función.

#!/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'

Declarar variables locales con «local»

El elemento integrado local restringe el ámbito de una variable a la función contenedora y a cualquier función que esta llame. Fuera de la función, la variable no está definida o conserva su valor anterior.

  • local varname — declara sin asignar.
  • local varname="value" — declara y asigna en un solo paso.
  • local -i count=0 — declara una variable local de tipo entero.
  • local -r PI=3.14159 — declara una constante local de solo lectura.

Buena práctica: declare como local todas las variables dentro de una función, a menos que necesite intencionadamente que sean globales.

#!/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 salida como valores de retorno

Las funciones de Bash no pueden devolver cadenas con return; return solo establece un estado de salida entero (0–255). Por convención:

  • return 0 — éxito
  • return 1 (o cualquier valor distinto de cero) — error

Quien llama lee el estado de salida mediante $? inmediatamente después de la llamada, o usa la función directamente en una condición if. Los códigos de salida son la forma idiomática de indicar éxito o error desde una función.

#!/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

Comunicar resultados de tipo cadena mediante printf

Cuando una función necesita devolver un resultado de tipo cadena, el patrón estándar consiste en imprimir en stdout y capturar el resultado con una sustitución de comandos $(). Se prefiere usar printf en lugar de echo porque:

  • printf no añade un salto de línea final de forma predeterminada (a menos que incluya \n).
  • El comportamiento de printf es coherente y está definido por POSIX; echo varía según el shell.
  • La sustitución de comandos elimina los saltos de línea finales, por lo que printf '%s' "$value" es 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"

Combinar códigos de salida y resultados en stdout

Una función bien diseñada puede tanto imprimir un resultado (si tiene éxito) como indicar un error (mediante el código de salida) al mismo tiempo. Quien llama decide qué hacer basándose en el código de salida antes de confiar en el resultado.

El patrón siguiente se usa ampliamente en bibliotecas reales de Bash:

  • Si tiene éxito: imprime el resultado con printf y ejecuta return 0.
  • Si falla: escribe un diagnóstico en stderr (no en stdout) y ejecuta return 1.
  • Escribir los errores en stderr mantiene stdout limpio para las tuberías.
#!/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

Usar «local» para proteger las funciones recursivas

La recursión demuestra con claridad por qué local es esencial. Cada llamada recursiva obtiene su propia copia independiente de cada variable local en la pila de llamadas. Sin local, cada llamada sobrescribiría la misma variable global y produciría resultados incorrectos.

La función factorial siguiente es segura porque n y sub son locales para cada marco de la pila.

#!/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

Evitar la trampa del subshell con local -n (Nameref)

La sustitución de comandos $() se ejecuta en un subshell. Las asignaciones de variables que se hagan dentro de él no son visibles para el shell padre. Cuando necesite que una función escriba en una variable proporcionada por quien la llama sin usar un subshell, utilice un nameref (local -n), disponible en Bash 4.3 y versiones posteriores.

  • local -n ref="$1" convierte ref en un alias de la variable cuyo nombre está almacenado en $1.
  • Asignar un valor a ref dentro de la función modifica directamente la variable de quien la llama.
  • Esto evita un subshell y mantiene locales los detalles de implementación.
#!/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]}"

Crear una pequeña biblioteca de funciones

Los proyectos de Bash del mundo real separan las funciones reutilizables en archivos de biblioteca que los scripts cargan mediante source (o el operador punto .). Reglas para diseñar buenas bibliotecas:

  • Toda variable dentro de una función de biblioteca debe ser local.
  • Las funciones de biblioteca nunca ejecutan exit: ejecutan return, para que quien las llama siga ejecutándose.
  • Use un prefijo de espacio de nombres coherente (por ejemplo, str_, log_) para evitar colisiones de nombres.
  • Evite cargar el mismo archivo dos veces mediante una variable centinela.

A continuación se muestra una biblioteca mínima de utilidades para cadenas que sigue estas convenciones.

#!/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

Validar argumentos dentro de las funciones

Las funciones que reciben argumentos deben validarlos al principio y devolver un código de salida específico si la entrada no es válida. Este patrón se denomina cláusula de guarda: fallar rápido y de forma clara.

  • Compruebe el número de argumentos con $#.
  • Valide los tipos o formatos antes de realizar cualquier operación.
  • Imprima los mensajes de diagnóstico únicamente en stderr, nunca en stdout.
  • Use códigos de retorno distintos de cero (por ejemplo, 1 = argumentos incorrectos, 2 = archivo no encontrado) para que quien llama pueda reaccionar de forma diferente a cada tipo de error.
#!/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

Integrarlo todo: un ejemplo del mundo real

Aquí tiene un script completo y autocontenido que demuestra cómo funcionan conjuntamente todos los conceptos de esta lección:

  • Variables local en todas las funciones.
  • Códigos de salida para indicar éxito o error.
  • printf para comunicar resultados de tipo cadena.
  • Errores escritos en stderr y resultados en stdout.
  • Cláusulas de guarda para validar argumentos.

Estudie el flujo: parse_version extrae los datos, version_ge los compara y main utiliza ambas funciones correctamente.

#!/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

Comprobación de conocimientos: variables locales y valores de retorno

Considere la siguiente función de Bash. ¿Cuál es la forma correcta de capturar su resultado de tipo cadena en quien la llama y qué afirmación sobre la variable tmp es verdadera?

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

Repaso: funciones con ámbito local y códigos de retorno

En esta lección ha aprendido a escribir funciones de Bash limpias, componibles y seguras:

  • Use siempre local para las variables dentro de las funciones y evitar contaminar el ámbito de quien las llama.
  • Use códigos de salida (return 0/1/N) para indicar éxito o error; se integran de forma natural con if, && y ||.
  • Use printf en stdout para comunicar resultados de tipo cadena y captúrelos con $() en quien llama.
  • Escriba los errores en stderr (>&2) para que stdout se mantenga limpio para el flujo de datos y las tuberías.
  • Use local -n (nameref) cuando necesite escribir en una variable proporcionada por quien llama sin el coste de un subshell.
  • Las cláusulas de guarda (validar los argumentos al principio y devolver el control inmediatamente si la entrada no es válida) hacen que las funciones sean robustas y fáciles de entender.
  • Los archivos de biblioteca deben cargarse mediante source, usar prefijos de espacio de nombres, no ejecutar nunca exit y evitar que se carguen dos veces.

Dominar estos patrones marca la diferencia entre scripts frágiles y puntuales y bases de código Bash profesionales y fáciles de mantener.

Preguntas frecuentes

¿La lección «Diseño de funciones con ámbito local y códigos de retorno» es gratis?

Sí — el texto completo de «Diseño de funciones con ámbito local y códigos de retorno» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de DevOps Bootcamp, actualiza a CoddyKit PRO. El curso de DevOps Bootcamp incluye 4 lecciones en total.

¿Qué aprenderé en «Diseño de funciones con ámbito local y códigos de retorno»?

Escriba funciones que usen variables locales, estados de salida y valores de retorno basados en printf en lugar de variables globales frágiles. Practicas DevOps Bootcamp con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar DevOps Bootcamp?

No se requiere experiencia previa. DevOps Bootcamp en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «Diseño de funciones con ámbito local y códigos de retorno»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de DevOps Bootcamp?

Sí. Cada lección de DevOps Bootcamp incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Diseño de funciones con ámbito local y códigos de retorno
  2. Creación y carga de bibliotecas Bash reutilizables
  3. Análisis de flags y argumentos con getopts
  4. Paso de matrices y mapas asociativos entre funciones
← Volver a DevOps Bootcamp