0Pricing
Linux Command Line & Bash Scripting Mastery · Lección

Controladores trap para limpieza y señales

Registre traps EXIT, ERR e INT para eliminar archivos temporales y revertir de forma fiable el trabajo parcial.

Controladores trap para limpieza y señales es una lección gratuita de Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Linux Command Line & Bash Scripting Mastery incluye 4 lecciones en total.

Por qué son importantes los manejadores de trap

Cuando un script de Bash termina — ya sea con normalidad, debido a un error o porque el usuario ha pulsado Ctrl+C— puede dejar archivos temporales, datos escritos parcialmente o un estado incoherente. Sin limpieza, estos residuos se acumulan y provocan problemas difíciles de depurar.

Los manejadores de trap resuelven este problema registrando una función o un comando que Bash ejecuta automáticamente al recibir una señal o pseudoseñal específica.

  • EXIT — se activa cada vez que el script termina, por cualquier motivo
  • ERR — se activa después de que cualquier comando devuelva un estado de salida distinto de cero
  • INT — se activa cuando el usuario envía SIGINT (Ctrl+C)
  • TERM — se activa con SIGTERM (por ejemplo, mediante kill)

Un script bien escrito registra estas traps al principio, antes de iniciar cualquier operación arriesgada.

Sintaxis del comando integrado trap

El comando integrado trap registra un manejador para una o más señales o pseudoseñales. La sintaxis general es:

trap 'command_or_function' SIGNAL [SIGNAL...]

Reglas principales:

  • El primer argumento es una cadena entre comillas de código de shell (o el nombre de una función) que se ejecutará cuando se active la señal.
  • Puede enumerar varias señales después del manejador.
  • trap '' SIGNAL ignora esa señal (manejador vacío).
  • trap - SIGNAL restablece la señal a su comportamiento predeterminado.

Las traps se heredan en las funciones llamadas en el mismo shell, pero no en los subshells iniciados con ( ) o &.

#!/usr/bin/env bash
# Syntax examples — not a full script

# Register a cleanup function on EXIT
trap cleanup EXIT

# Inline handler for INT and TERM
trap 'echo "Interrupted!"; exit 1' INT TERM

# Ignore SIGHUP
trap '' HUP

# Reset SIGPIPE to default
trap - PIPE

La trap EXIT: su red de seguridad

La pseudoseñal EXIT es la trap más importante que debe registrar. Se activa cuando el shell termina por cualquier motivo: finalización normal, exit N, un error no controlado o una señal. Esto la hace ideal para eliminar archivos temporales.

Práctica recomendada:

  • Cree archivos temporales con mktemp para que el nombre sea único e impredecible.
  • Guarde la ruta en una variable inmediatamente.
  • Registre la trap EXIT justo después de crearlo para que la limpieza se ejecute siempre.

El código de salida del script se conserva: el código de salida del manejador de trap se ignora, a menos que llame explícitamente a exit dentro de él.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/myapp.XXXXXX)

cleanup() {
    echo "Removing temp file: $TMPFILE" >&2
    rm -f "$TMPFILE"
}
trap cleanup EXIT

# Do real work
echo "Processing..." > "$TMPFILE"
sort "$TMPFILE" -o "$TMPFILE"
cp "$TMPFILE" /tmp/myapp_result.txt

echo "Done. Result saved."

Gestión de varios archivos y directorios temporales

Los scripts reales suelen crear varios recursos temporales: archivos, directorios, FIFOs y archivos de bloqueo. En lugar de realizar el seguimiento de cada uno por separado, reúnalos en un array y elimínelos todos mediante una única función de limpieza.

Este patrón se amplía fácilmente: cada vez que cree un nuevo recurso temporal, añádalo al array. La trap EXIT siempre gestiona la lista completa, independientemente del punto en el que termine el script.

#!/usr/bin/env bash
set -euo pipefail

CLEANUP_TARGETS=()

add_cleanup() {
    CLEANUP_TARGETS+=("$1")
}

cleanup() {
    echo "Running cleanup..." >&2
    for target in "${CLEANUP_TARGETS[@]:-}"; do
        rm -rf "$target"
        echo "Removed: $target" >&2
    done
}
trap cleanup EXIT

# Create and track temp resources
TMPDIR_WORK=$(mktemp -d /tmp/work.XXXXXX)
add_cleanup "$TMPDIR_WORK"

TMPFILE_LOG=$(mktemp /tmp/run.XXXXXX.log)
add_cleanup "$TMPFILE_LOG"

echo "Working in $TMPDIR_WORK" | tee "$TMPFILE_LOG"
touch "$TMPDIR_WORK/output.txt"
echo "Step complete."

La trap ERR: captura de fallos

La pseudoseñal ERR se activa después de que cualquier comando simple devuelva un estado de salida distinto de cero, siempre que la opción de shell set -e (errexit) esté activa o el comando no se encuentre dentro de una construcción compuesta if, while o ||/&&.

Use la trap ERR para:

  • Registrar qué línea ha fallado mediante $LINENO y $BASH_COMMAND.
  • Activar la lógica de reversión antes de que se ejecute la trap EXIT.
  • Emitir mensajes de error estructurados en stderr.

Importante: las traps ERR no se activan dentro de las funciones, a menos que ejecute explícitamente set -E (errtrace) para que las funciones hereden la trap ERR del shell que las llama.

#!/usr/bin/env bash
set -eEuo pipefail

on_error() {
    local exit_code=$?
    local line=$1
    echo "ERROR: command failed with exit code $exit_code at line $line" >&2
    echo "  Command: $BASH_COMMAND" >&2
}
trap 'on_error $LINENO' ERR

echo "Starting task..."
cp /nonexistent/file /tmp/   # This will fail
echo "This line is never reached."

La trap INT: Ctrl+C controlado

Al pulsar Ctrl+C se envía SIGINT al grupo de procesos en primer plano. Sin una trap, Bash termina inmediatamente y puede dejar trabajo parcialmente realizado.

Registrar una trap INT le permite:

  • Mostrar un mensaje de cancelación claro para el usuario.
  • Realizar cualquier reversión intermedia antes de delegar en la trap EXIT.
  • Terminar con un código de estado significativo (130 es el código convencional para SIGINT: 128 + 2).

Cuando termine el manejador INT, llame a exit 130 para propagar el código de salida correcto. La trap EXIT se ejecutará automáticamente a continuación.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/demo.XXXXXX)

cleanup() {
    rm -f "$TMPFILE"
    echo "Temp file removed." >&2
}

on_interrupt() {
    echo "" >&2
    echo "Caught SIGINT — cancelling gracefully." >&2
    exit 130   # EXIT trap (cleanup) runs automatically after this
}

trap cleanup EXIT
trap on_interrupt INT

echo "Running long task. Press Ctrl+C to cancel."
for i in $(seq 1 10); do
    echo "Step $i/10..."
    sleep 1
done
echo "All steps complete."

Combinación de las traps EXIT, ERR e INT

En los scripts de producción, normalmente registra las tres traps juntas para cubrir todas las vías de salida:

  • EXIT — siempre se ejecuta; gestiona la limpieza de archivos.
  • ERR — registra el comando que ha fallado y el número de línea.
  • INT — muestra un mensaje de cancelación y termina con el código 130.

Como EXIT siempre se ejecuta al final, la lógica de limpieza solo debe estar en un lugar. Los manejadores ERR e INT pueden centrarse en registrar información y establecer el estado, y delegar la limpieza real en EXIT mediante una llamada a exit.

Use set -eEuo pipefail al principio para que los errores en las funciones también activen ERR y para que pipefail detecte los fallos dentro de las tuberías.

#!/usr/bin/env bash
set -eEuo pipefail

TMPFILE=$(mktemp /tmp/combined.XXXXXX)
ROLLBACK_NEEDED=false

cleanup() {
    if [[ "$ROLLBACK_NEEDED" == true ]]; then
        echo "Rolling back partial changes..." >&2
    fi
    rm -f "$TMPFILE"
    echo "Cleanup done." >&2
}

on_error() {
    ROLLBACK_NEEDED=true
    echo "ERR at line $1: $BASH_COMMAND" >&2
}

on_interrupt() {
    echo "Interrupted by user." >&2
    exit 130
}

trap cleanup       EXIT
trap 'on_error $LINENO' ERR
trap on_interrupt  INT

echo "hello" > "$TMPFILE"
echo "Script finished successfully."

Restablecimiento y desactivación dinámica de traps

A veces necesita modificar una trap durante la ejecución del script; por ejemplo, para realizar una limpieza puntual cuando finalice una sección crítica y evitar que las operaciones normales posteriores activen una reversión innecesaria.

  • trap - SIGNAL restablece la señal a su comportamiento predeterminado integrado.
  • trap '' SIGNAL ignora completamente la señal (el proceso no puede ser terminado mediante ella).
  • Puede volver a registrar un nuevo manejador en cualquier momento; prevalece la última llamada a trap.

Un patrón habitual consiste en actualizar la trap EXIT una vez que una operación arriesgada se realiza correctamente, sustituyendo el manejador de reversión por un manejador de limpieza sencillo.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/staged.XXXXXX)

rollback() {
    echo "ROLLBACK: removing $TMPFILE" >&2
    rm -f "$TMPFILE"
}

cleanup_only() {
    echo "CLEANUP: removing $TMPFILE" >&2
    rm -f "$TMPFILE"
}

# Start with rollback in case something fails during the risky phase
trap rollback EXIT

echo "Performing risky operation..."
echo "critical data" > "$TMPFILE"
# ... imagine more risky steps here ...

# Risky phase succeeded — switch to simple cleanup
trap cleanup_only EXIT

echo "Risky phase done. Now doing safe finalization."
cp "$TMPFILE" /tmp/staged_result.txt
echo "All done."

Propagación de códigos de salida mediante traps

Existe un problema sutil: si la función de limpieza contiene comandos que pueden fallar, el código de salida final del script podría sobrescribirse. Conserve explícitamente el código de salida original.

Dentro de un manejador de trap, $? contiene el código de salida que activó la trap. Captúrelo en la primera línea del manejador, antes de que cualquier otro comando lo modifique.

#!/usr/bin/env bash
set -euo pipefail

TMPFILE=$(mktemp /tmp/exitcode.XXXXXX)

cleanup() {
    local original_exit=$?   # capture BEFORE any other command
    echo "Cleaning up (original exit: $original_exit)" >&2
    rm -f "$TMPFILE"
    # Exit with the original code so callers see the real result
    exit "$original_exit"
}
trap cleanup EXIT

echo "Doing work..."
echo "data" > "$TMPFILE"

# Simulate failure
if [[ ! -f /tmp/required_marker ]]; then
    echo "Required marker missing!" >&2
    exit 2
fi

echo "Done."

Traps en funciones y subshells

Comprender la herencia de las traps es fundamental para escribir scripts correctos:

  • Las funciones se ejecutan en el mismo shell y heredan las traps, pero ERR solo se propaga a las funciones cuando set -E (errtrace) está activo.
  • Los subshells (( ... )) comienzan con una copia de las traps del proceso padre, pero los cambios realizados dentro del subshell no afectan al padre.
  • Los trabajos en segundo plano (cmd &) son subshells y no heredan las traps INT/TERM del proceso padre (reciben la disposición predeterminada de las señales).

Regla práctica: mantenga el registro de traps en el cuerpo principal del script, use set -E cuando necesite que ERR se propague a las funciones y no dependa nunca de la herencia de traps a través de &.

#!/usr/bin/env bash
set -eEuo pipefail   # -E = errtrace: ERR propagates into functions

on_error() {
    echo "ERR in function or main at line $1" >&2
}
trap 'on_error $LINENO' ERR

risky_function() {
    echo "Inside risky_function"
    ls /nonexistent_path   # triggers ERR — works because of set -E
}

# Subshell: has its own copy; changes don't affect parent
(
    trap '' ERR   # disable ERR only inside subshell
    ls /nonexistent_path 2>/dev/null || true
    echo "Subshell completed without triggering parent ERR"
)

risky_function

Patrón del mundo real: sustitución atómica de archivos

Un patrón clásico de Bash defensivo es la sustitución atómica de archivos: escribir en un archivo temporal y cambiarle después el nombre para colocarlo en su ubicación definitiva. Si algo falla antes del cambio de nombre, el archivo original permanece intacto. Las traps hacen que este proceso sea robusto.

Pasos:

  1. Cree un archivo temporal en el mismo sistema de archivos que el destino (para que mv sea atómico).
  2. Registre una trap EXIT para eliminar el archivo temporal si terminamos antes de tiempo.
  3. Escriba y valide el contenido nuevo.
  4. Cambie el nombre de forma atómica (mv); solo en este momento se reemplaza el original.
  5. La trap EXIT elimina el archivo temporal únicamente si todavía existe (después de un mv correcto, ya no existe).
#!/usr/bin/env bash
set -euo pipefail

DEST="/tmp/important_config.conf"
TMPFILE=$(mktemp "$(dirname "$DEST")/.tmp.XXXXXX")

cleanup() {
    rm -f "$TMPFILE"   # no-op if mv already moved it
}
trap cleanup EXIT

# Write new content to temp file
cat > "$TMPFILE" <<'EOF'
[settings]
version=2
mode=production
EOF

# Validate before replacing
if ! grep -q 'version=' "$TMPFILE"; then
    echo "Validation failed — aborting replacement" >&2
    exit 1
fi

# Atomic rename — DEST is replaced only here
mv "$TMPFILE" "$DEST"
echo "Config updated atomically: $DEST"

Comprobación de conocimientos: herencia de la trap ERR

Compruebe su comprensión del comportamiento de la trap ERR con las funciones en Bash.

Repaso: manejadores de trap para limpieza y señales

En esta lección ha aprendido a hacer que los scripts de Bash sean resistentes mediante manejadores de trap:

  • EXIT es su red de seguridad universal: regístrela inmediatamente después de crear cualquier recurso temporal y capture siempre primero $? para conservar el código de salida original.
  • ERR permite registrar los fallos con $LINENO y $BASH_COMMAND; use set -E para que las funciones la hereden.
  • INT gestiona Ctrl+C correctamente: muestre un mensaje y termine con el código 130 para que EXIT realice la limpieza.
  • Reúna los recursos temporales en un array y elimínelos todos mediante una única función de limpieza.
  • Use trap - SIGNAL para restablecer las señales y trap '' SIGNAL para ignorarlas dinámicamente.
  • El patrón de cambio de nombre atómico (mktemp → escribir → validar → mv), combinado con una trap EXIT, garantiza que las escrituras parciales nunca corrompan el archivo de destino.

Combinar set -eEuo pipefail con traps bien ubicadas proporciona una base defensiva sólida para cualquier script de Bash de producción.

Preguntas frecuentes

¿La lección «Controladores trap para limpieza y señales» es gratis?

Sí — el texto completo de «Controladores trap para limpieza y señales» 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 Linux Command Line & Bash Scripting Mastery, actualiza a CoddyKit PRO. El curso de Linux Command Line & Bash Scripting Mastery incluye 4 lecciones en total.

¿Qué aprenderé en «Controladores trap para limpieza y señales»?

Registre traps EXIT, ERR e INT para eliminar archivos temporales y revertir de forma fiable el trabajo parcial. Practicas Linux Command Line & Bash Scripting Mastery 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 Linux Command Line & Bash Scripting Mastery?

No se requiere experiencia previa. Linux Command Line & Bash Scripting Mastery 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 «Controladores trap para limpieza y señales»?

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 Linux Command Line & Bash Scripting Mastery?

Sí. Cada lección de Linux Command Line & Bash Scripting Mastery 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. Modo estricto con set -euo pipefail
  2. Controladores trap para limpieza y señales
  3. Archivos temporales seguros y directorios de bloqueo
  4. Scripts idempotentes y lógica de reintento con backoff
← Volver a Linux Command Line & Bash Scripting Mastery