0Pricing
DevOps Bootcamp · Lección

Creación y carga de bibliotecas Bash reutilizables

Organice los helpers compartidos en archivos de biblioteca .sh que puedan cargarse con source, usando protecciones contra inclusiones repetidas y prefijos de funciones con espacio de nombres.

Creación y carga de bibliotecas Bash reutilizables es una lección gratuita de DevOps Bootcamp en CoddyKit. Esta es la lección 2 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.

¿Qué es una biblioteca de Bash?

En ingeniería de software, una biblioteca es un conjunto de funciones reutilizables que pueden compartir varios programas. Bash admite el mismo concepto mediante scripts de shell que se pueden cargar.

En lugar de copiar y pegar funciones auxiliares en cada script, colóquelas en un archivo .sh independiente y cárguelo con el comando source (o su abreviatura .). Todas las funciones, variables o alias definidos en ese archivo estarán disponibles en la sesión actual del shell del script que lo carga.

  • Fomenta el principio DRY (Don't Repeat Yourself, no se repita)
  • Centraliza las correcciones de errores: se corrige una vez y todos los usuarios se benefician
  • Hace que los scripts individuales sean más cortos y fáciles de leer
  • Permite mantener la coherencia de todo el equipo en el registro, el manejo de errores y la lógica de utilidades

Un proyecto de Bash bien estructurado suele tener un directorio lib/ que contiene estos archivos compartidos, siguiendo las convenciones de los lenguajes de alto nivel.

El comando source y el operador punto

Hay dos formas equivalentes de cargar un archivo de biblioteca en el entorno del shell actual:

  • source /path/to/lib.sh — la forma explícita y legible
  • . /path/to/lib.sh — la abreviatura compatible con POSIX

Ambas ejecutan el archivo en el proceso actual del shell, no en un subshell, por lo que todas las funciones y variables definidas en él pasan a formar parte del entorno del script inmediatamente después de la llamada.

Un patrón habitual consiste en localizar la biblioteca en relación con el script que la llama mediante $BASH_SOURCE, lo que hace que el proyecto sea portable independientemente de dónde se instale.

#!/usr/bin/env bash
# main.sh — load a library relative to this script's own location

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "${SCRIPT_DIR}/lib/utils.sh"

echo "Library loaded. Calling greet..."
greet "World"

Crear el primer archivo de biblioteca

Un archivo de biblioteca es un archivo .sh normal que contiene únicamente definiciones de funciones (y, ocasionalmente, constantes). No debe ejecutar código con efectos secundarios en el nivel superior: está pensado para cargarse mediante source, no para ejecutarse directamente.

Convenciones principales:

  • Comience con un comentario de shebang que describa el propósito de la biblioteca
  • Defina únicamente funciones; no incluya lógica de main en el nivel superior
  • Use return dentro de las funciones (nunca exit, que finalizaría el proceso que la llama)
  • Conserve el archivo en un subdirectorio lib/ del proyecto
#!/usr/bin/env bash
# lib/utils.sh — General-purpose utility functions

# Print a greeting message
greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

# Print a timestamped log line to stderr
log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

# Print an error message and return a failure code
log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

Protectores de inclusión: evitar cargar dos veces

Cuando varios scripts cargan la misma biblioteca, o cuando una biblioteca carga otra que también carga el script principal, las funciones pueden definirse varias veces. Esto desperdicia tiempo y puede causar errores sutiles si el cuerpo de una función se reemplaza durante la ejecución.

La solución es un protector de inclusión: una variable que actúa como indicador. En la primera carga, la variable no está definida, por lo que el archivo continúa. En las cargas posteriores, el protector ya está establecido, por lo que el archivo devuelve el control inmediatamente.

Este es el equivalente en Bash de #pragma once en C/C++ o de los patrones if not already imported en otros lenguajes.

#!/usr/bin/env bash
# lib/utils.sh — with include guard

# Guard: if already sourced, do nothing
[[ -n "${_LIB_UTILS_LOADED:-}" ]] && return 0
_LIB_UTILS_LOADED=1

greet() {
    local name="${1:-stranger}"
    echo "Hello, ${name}!"
}

log_info() {
    echo "[INFO]  $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
}

log_error() {
    echo "[ERROR] $(date '+%Y-%m-%d %H:%M:%S')  $*" >&2
    return 1
}

Prefijos de funciones con espacio de nombres

Bash tiene un único espacio de nombres global para las funciones. Si dos bibliotecas definen una función llamada log o init, la segunda definición sobrescribe silenciosamente la primera.

La defensa estándar es un prefijo de espacio de nombres: cada función de una biblioteca lleva como prefijo el nombre corto de la biblioteca seguido de dos dos puntos (::) o un guion bajo. Por ejemplo, una biblioteca de utilidades para cadenas usa str:: y una biblioteca de archivos usa file::.

  • Las colisiones se vuelven extremadamente improbables
  • El código que realiza la llamada se explica por sí mismo: str::trim indica exactamente dónde se encuentra la función
  • Mejora la facilidad de búsqueda con grep: grep 'str::' main.sh muestra al instante todas las llamadas a la biblioteca de cadenas
#!/usr/bin/env bash
# lib/str.sh — String utility library (namespaced)

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

# Trim leading and trailing whitespace
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

# Convert string to uppercase
str::upper() {
    echo "${1^^}"
}

# Convert string to lowercase
str::lower() {
    echo "${1,,}"
}

# Check if a string contains a substring
str::contains() {
    [[ "$1" == *"$2"* ]]
}

Organizar un directorio lib/

A medida que crece un proyecto, un único archivo utils.sh se vuelve difícil de manejar. Separe las responsabilidades en archivos de biblioteca especializados dentro de un directorio lib/:

  • lib/log.sh — utilidades de registro (log::info, log::warn, log::error)
  • lib/str.sh — manipulación de cadenas (str::trim, str::upper)
  • lib/fs.sh — utilidades del sistema de archivos (fs::require_dir, fs::safe_rm)
  • lib/net.sh — comprobaciones de red (net::wait_for_port, net::is_online)

Un único archivo cargador de arranque (lib/bootstrap.sh) puede cargar todos ellos en el orden correcto, de modo que cada script solo necesite una llamada a source.

#!/usr/bin/env bash
# lib/bootstrap.sh — Load all project libraries in dependency order

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

_BOOTSTRAP_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

source "${_BOOTSTRAP_DIR}/log.sh"
source "${_BOOTSTRAP_DIR}/str.sh"
source "${_BOOTSTRAP_DIR}/fs.sh"
source "${_BOOTSTRAP_DIR}/net.sh"

log::info "All libraries loaded."

Una biblioteca completa de registro

El registro es una preocupación común a la mayoría de los scripts. Una lib/log.sh dedicada centraliza el formato de salida, los niveles de registro y los códigos de color.

Con una biblioteca así, todos los scripts del proyecto muestran mensajes coherentes, con marca de tiempo y códigos de color, sin duplicar código de formato.

#!/usr/bin/env bash
# lib/log.sh — Coloured, levelled logging library

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

# Colour codes (disabled when not writing to a terminal)
_LOG_RED=''; _LOG_YEL=''; _LOG_GRN=''; _LOG_RST=''
if [[ -t 2 ]]; then
    _LOG_RED='\033[0;31m'
    _LOG_YEL='\033[0;33m'
    _LOG_GRN='\033[0;32m'
    _LOG_RST='\033[0m'
fi

_log::_print() {
    local level="$1" colour="$2"; shift 2
    printf "%b[%s]%b %s  %s\n" \
        "$colour" "$level" "$_LOG_RST" \
        "$(date '+%H:%M:%S')" "$*" >&2
}

log::info()  { _log::_print 'INFO ' "$_LOG_GRN" "$@"; }
log::warn()  { _log::_print 'WARN ' "$_LOG_YEL" "$@"; }
log::error() { _log::_print 'ERROR' "$_LOG_RED" "$@"; return 1; }
log::fatal() { _log::_print 'FATAL' "$_LOG_RED" "$@"; exit 1; }

Una biblioteca de utilidades del sistema de archivos

Los scripts que manipulan archivos y directorios suelen repetir las mismas comprobaciones preventivas: ¿existe este directorio? ¿Se puede escribir en esta ruta? ¿Está a punto de eliminar algo importante?

Centralizar estas comprobaciones en lib/fs.sh hace que todos los scripts que la utilizan sean más seguros y legibles. Observe que cada función usa return 1 en caso de error en lugar de exit, lo que permite al código llamador gestionar el error correctamente.

#!/usr/bin/env bash
# lib/fs.sh — Filesystem helper library

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

# Ensure a directory exists; create it if not
fs::require_dir() {
    local dir="$1"
    if [[ ! -d "$dir" ]]; then
        mkdir -p "$dir" || { echo "[fs] Cannot create directory: $dir" >&2; return 1; }
    fi
}

# Remove a file only if it exists (no error on missing)
fs::safe_rm() {
    local target="$1"
    [[ -e "$target" ]] && rm -rf -- "$target"
    return 0
}

# Assert that a file exists and is readable
fs::require_file() {
    local file="$1"
    [[ -f "$file" && -r "$file" ]] || {
        echo "[fs] Required file missing or unreadable: $file" >&2
        return 1
    }
}

Versionar la biblioteca con una constante

Cuando las bibliotecas se comparten entre varios proyectos o se distribuyen a un equipo, es importante saber qué versión de una biblioteca se ha cargado durante la ejecución. Una convención sencilla consiste en exportar una constante de versión desde cada biblioteca.

De este modo, el código llamador puede comprobar una versión mínima al iniciarse y detectar pronto las incompatibilidades, en lugar de tener que depurar fallos misteriosos más adelante. La variable de protección también sirve como cadena de versión, por lo que una sola variable cumple ambas funciones.

#!/usr/bin/env bash
# lib/str.sh — versioned example

# Guard doubles as the version identifier
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
readonly _LIB_STR_LOADED='1.3.0'

# Caller can validate the version
str::version() { echo "$_LIB_STR_LOADED"; }

# ---- Utility functions ----
str::trim() {
    local s="$1"
    s="${s#"${s%%[![:space:]]*}"}"
    s="${s%"${s##*[![:space:]]}"}"  
    echo "$s"
}

str::repeat() {
    local str="$1" count="$2" result=''
    for (( i=0; i<count; i++ )); do result+="$str"; done
    echo "$result"
}

Una demostración autónoma: usar varias bibliotecas

Esta escena muestra un script realista que carga dos bibliotecas y utiliza funciones de cada una. Observe cómo el script principal se mantiene limpio: expresa la intención, mientras que todos los detalles de implementación residen en las bibliotecas.

El script se puede ejecutar como archivo independiente porque define las bibliotecas en línea mediante here-documents escritos en archivos temporales. En un proyecto real, cada biblioteca estaría en su propio archivo dentro de lib/.

#!/usr/bin/env bash
# Standalone demo: inline libs written to /tmp, then sourced
set -euo pipefail

# --- Create a temporary lib/log.sh ---
TMPDIR_LIBS="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_LIBS"' EXIT

cat > "${TMPDIR_LIBS}/log.sh" <<'LIBEOF'
[[ -n "${_LIB_LOG_LOADED:-}" ]] && return 0
_LIB_LOG_LOADED=1
log::info()  { echo "[INFO]  $*"; }
log::error() { echo "[ERROR] $*" >&2; return 1; }
LIBEOF

cat > "${TMPDIR_LIBS}/str.sh" <<'LIBEOF'
[[ -n "${_LIB_STR_LOADED:-}" ]] && return 0
_LIB_STR_LOADED=1
str::upper() { echo "${1^^}"; }
str::trim()  { local s="$1"; s="${s#"${s%%[![:space:]]*}"}";
               s="${s%"${s##*[![:space:]]}"}"  ; echo "$s"; }
LIBEOF

# --- Source both libraries ---
source "${TMPDIR_LIBS}/log.sh"
source "${TMPDIR_LIBS}/str.sh"

# --- Main logic ---
log::info "Libraries loaded successfully."
raw_input="   hello from bash libraries   "
trimmed="$(str::trim "$raw_input")"
log::info "Trimmed: '${trimmed}'"
log::info "Uppercased: '$(str::upper "$trimmed")'"

Buenas prácticas y errores frecuentes

Antes de distribuir una biblioteca para que la use el equipo, repase esta lista de comprobación:

  • Protección de inclusión — todas las bibliotecas deben tener una; documente el nombre de la variable de protección al principio
  • Sin efectos secundarios en el nivel superior — nunca use cd, echo ni modifique el estado global fuera del cuerpo de una función
  • Use local para todas las variables dentro de las funciones — sin local, cada asignación se filtra al ámbito del código llamador
  • Devuelva valores; nunca use exit — exit dentro de un archivo cargado termina todo el shell que lo ha llamado
  • Valide las entradas — compruebe los argumentos obligatorios y devuelva un código de error significativo cuando falten
  • Documente con comentarios — describa qué hace cada función, sus parámetros y su valor de retorno
  • Evite set -e dentro de los archivos de biblioteca — el código llamador puede tener su propia estrategia de gestión de errores; deje que la decida

Comprobación de conocimientos: protecciones de inclusión

Imagine un proyecto en el que main.sh carga tanto lib/bootstrap.sh como lib/log.sh, y lib/bootstrap.sh también carga internamente lib/log.sh. ¿Cuál es el objetivo principal de la protección de inclusión en lib/log.sh?

Repaso de la lección: bibliotecas Bash bien hechas

Ha aprendido todas las técnicas esenciales para crear y utilizar bibliotecas Bash reutilizables. Esto es lo más importante:

  • Cargue con source o . — carga un archivo en el shell actual y hace que sus funciones estén disponibles de inmediato
  • Use $BASH_SOURCE para resolver las rutas de las bibliotecas relativas al script que las llama y mantener la portabilidad de los proyectos
  • Protecciones de inclusión ([[ -n "${_GUARD:-}" ]] && return 0) evitan definiciones duplicadas cuando varios archivos cargan la misma biblioteca
  • Prefijos de espacio de nombres (log::, str::, fs::) eliminan las colisiones entre nombres de funciones de distintas bibliotecas
  • Un directorio lib/ con archivos especializados y de responsabilidad única facilita el mantenimiento de los proyectos grandes
  • Un cargador de arranque (lib/bootstrap.sh) permite que cada script cargue todo el ecosistema mediante una única llamada a source
  • No use nunca exit ni efectos secundarios en el nivel superior en los archivos de biblioteca; allí solo deben definirse funciones y constantes

Aplique estos patrones de forma coherente y sus scripts de shell serán tan modulares y fáciles de mantener como el código escrito en cualquier lenguaje de alto nivel.

Preguntas frecuentes

¿La lección «Creación y carga de bibliotecas Bash reutilizables» es gratis?

Sí — el texto completo de «Creación y carga de bibliotecas Bash reutilizables» 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 «Creación y carga de bibliotecas Bash reutilizables»?

Organice los helpers compartidos en archivos de biblioteca .sh que puedan cargarse con source, usando protecciones contra inclusiones repetidas y prefijos de funciones con espacio de nombres. 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 2 de 4.

¿Cuánto tiempo toma la lección «Creación y carga de bibliotecas Bash reutilizables»?

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