Curso de Linux / Manejar errores en Bash y copiar archivos con comprobaciones
Los scripts de archivos seguros primero verifican la entrada y el destino, manejan explícitamente los fallos de las operaciones y no deben dejar resultados intermedios fallidos como si fueran resultados válidos. En lugar de depender únicamente de set -e, defina cómo tratar el fallo de cada comando y diseñe la limpieza de archivos temporales y las condiciones de sobrescritura.
Este artículo, basado en el primer script de Bash, aborda las ramas de fallo y los límites de las operaciones de archivos. Primero consulte las reglas básicas de ejecución de tuberías y operadores &&·|| en flujo de ejecución del shell.
Definir el alcance a garantizar en la práctica
El ejemplo copia un archivo regular a un nombre nuevo y comprueba que conserva su contenido. Requiere un directorio de destino existente, privado y confiable; durante la ejecución ningún otro proceso debe cambiar el origen ni las rutas de destino. Se realiza como usuario normal y no sustituye una copia consistente de una base de datos en uso. La preparación está encadenada con &&: si falla un paso, no se ejecutan los posteriores. Deténgase ante un fallo antes de pasar al bloque siguiente.
Ejemplo completo separado en comprobación, copia temporal y publicación
lab=$(mktemp -d "$HOME/linux-safe-copy.XXXXXX") &&
cd "$lab" &&
mkdir output &&
printf "important sample\n" > "source file.txt" &&
cat > checked-copy.sh <<'EOF' &&
#!/usr/bin/env bash
set -u
set -o pipefail
umask 077
if (( $# != 2 )); then
printf "Usage: %s SOURCE DESTINATION\n" "$0" >&2
exit 2
fi
source_file=$1
destination=$2
if [[ ! -f "$source_file" || -L "$source_file" ]]; then
printf "Source must be a regular non-symlink file\n" >&2
exit 1
fi
if [[ -e "$destination" || -L "$destination" ]]; then
printf "Destination already exists\n" >&2
exit 1
fi
parent=$(dirname -- "$destination")
if [[ ! -d "$parent" ]]; then
printf "Destination directory is missing\n" >&2
exit 1
fi
work=$(mktemp -d "$parent/.copy-work.XXXXXX") || exit 1
cleanup() { rm -rf -- "$work"; }
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
if ! cp -- "$source_file" "$work/payload"; then
printf "Copy failed\n" >&2
exit 1
fi
if ! cmp -s -- "$source_file" "$work/payload"; then
printf "Verification failed\n" >&2
exit 1
fi
if ! ln -- "$work/payload" "$destination"; then
printf "Destination creation failed\n" >&2
exit 1
fi
printf "Created: %s\n" "$destination"
EOF
bash -n checked-copy.sh &&
bash checked-copy.sh "source file.txt" "output/copy file.txt" &&
cmp "source file.txt" "output/copy file.txt"
El programa copia a un directorio temporal del mismo sistema de archivos que el destino, compara el contenido y crea el nombre final mediante un enlace duro con ln. El control inicial rechaza destinos existentes y ln sin -f no reemplaza otro archivo regular. Al eliminar el nombre temporal, el enlace final conserva el contenido. Si el sistema de archivos no admite enlaces duros, el paso falla y debe revisar los requisitos en lugar de forzar otra operación.
Este ejemplo verifica la copia de contenido, pero no garantiza la preservación de propiedad, ACLs, atributos extendidos ni la durabilidad física de la escritura completada. Tampoco es adecuado para tareas en las que el original se modifica durante la copia. rm -rf solo apunta a una ruta de trabajo creada exitosamente por mktemp, y no se usa en las rutas de origen o destino proporcionadas.
Probar cambiando realmente las condiciones de fallo
bash checked-copy.sh "source file.txt" "output/copy file.txt"
printf "exit=%s\n" "$?"
bash checked-copy.sh missing.txt output/new.txt
printf "exit=%s\n" "$?"
find output -maxdepth 1 -name ".copy-work.*" -print
La primera reejecución termina con el estado 1 porque el destino ya existe, y se debe preservar el contenido existente. Un origen que no existe también debe fallar, y después de una terminación normal o un fallo general, la última búsqueda con find no debe mostrar la ruta de trabajo temporal. En caso de SIGKILL o pérdida de energía, trap no se ejecuta, por lo que se necesita una política de limpieza separada para los archivos restantes. Después del experimento de fallo, verifique nuevamente con cmp si el original y la primera copia son iguales.
No confíe demasiado en set -e y pipefail
bash -c 'false | cat; printf "status=%s\n" "$?"'
bash -o pipefail -c 'false | cat; printf "status=%s\n" "$?"'
El primer ejemplo imprime status=0 y el segundo, status=1: esos valores corresponden a la tubería justo antes de printf. Ambos procesos bash terminan con estado 0 porque su última orden es ese printf exitoso. pipefail modifica el estado de la tubería, pero no recupera automáticamente una operación. set -e tiene excepciones en condiciones, listas y tuberías; pruebe en qué punto se detiene realmente el programa.
if ! command; then es conveniente para escribir ramas de fallo, pero $? dentro de él no conserva el código de salida original porque sigue a una operación de negación. Si se debe mantener el código original, guárdelo inmediatamente después del fallo como if command; then ...; else rc=$?; ...; fi. Diseñe también para que los códigos de retorno y mensajes de error devueltos por el script puedan ser distinguidos por el llamador.
Antes de aplicarlo a la automatización operativa, verifique lo siguiente:
- Proteja la expansión de variables con comillas y compruebe si se admite -- antes de los nombres de archivo.
- Separe el modo de vista previa del modo de cambio real y asegúrese de que los datos no cambien durante la vista previa.
- Revise la propiedad y los permisos de las rutas, los enlaces simbólicos, la ejecución simultánea y la posibilidad de cambio de los originales.
- Pruebe por separado éxito, fallo y terminación por señal, y deje backups que puedan recuperarse.




