Crea scripts seguros y robustos validando todas las entradas del usuario
La validación de entradas es crucial para crear scripts robustos y seguros. Un script bien diseñado debe manejar graciosamente entradas inválidas, proporcionar mensajes de error claros y prevenir comportamientos inesperados.
#!/bin/bash
# Funciones básicas de validación
# Validar que no esté vacío
validar_no_vacio() {
local valor="$1"
local nombre="$2"
if [ -z "$valor" ]; then
echo "Error: $nombre no puede estar vacío" >&2
return 1
fi
return 0
}
# Validar longitud mínima
validar_longitud_min() {
local valor="$1"
local minimo="$2"
local nombre="$3"
if [ ${#valor} -lt $minimo ]; then
echo "Error: $nombre debe tener al menos $minimo caracteres" >&2
return 1
fi
return 0
}
# Validar longitud máxima
validar_longitud_max() {
local valor="$1"
local maximo="$2"
local nombre="$3"
if [ ${#valor} -gt $maximo ]; then
echo "Error: $nombre no puede tener más de $maximo caracteres" >&2
return 1
fi
return 0
}
# Validar que sea solo letras
validar_solo_letras() {
local valor="$1"
local nombre="$2"
if [[ ! "$valor" =~ ^[a-zA-ZáéíóúÁÉÍÓÚñÑ]+$ ]]; then
echo "Error: $nombre debe contener solo letras" >&2
return 1
fi
return 0
}
# Validar que sea solo números
validar_solo_numeros() {
local valor="$1"
local nombre="$2"
if [[ ! "$valor" =~ ^[0-9]+$ ]]; then
echo "Error: $nombre debe contener solo números" >&2
return 1
fi
return 0
}
# Ejemplo de uso
main() {
local nombre="$1"
local edad="$2"
local telefono="$3"
echo "=== VALIDACIÓN DE DATOS ==="
# Validar nombre
if validar_no_vacio "$nombre" "Nombre" && \
validar_longitud_min "$nombre" 2 "Nombre" && \
validar_longitud_max "$nombre" 50 "Nombre" && \
validar_solo_letras "$nombre" "Nombre"; then
echo "✓ Nombre válido: $nombre"
else
echo "✗ Nombre inválido"
return 1
fi
# Validar edad
if validar_no_vacio "$edad" "Edad" && \
validar_solo_numeros "$edad" "Edad"; then
if [ "$edad" -ge 0 ] && [ "$edad" -le 120 ]; then
echo "✓ Edad válida: $edad años"
else
echo "✗ Edad debe estar entre 0 y 120 años"
return 1
fi
else
echo "✗ Edad inválida"
return 1
fi
# Validar teléfono (opcional)
if [ -n "$telefono" ]; then
if validar_solo_numeros "$telefono" "Teléfono" && \
validar_longitud_min "$telefono" 10 "Teléfono" && \
validar_longitud_max "$telefono" 15 "Teléfono"; then
echo "✓ Teléfono válido: $telefono"
else
echo "✗ Teléfono inválido"
return 1
fi
else
echo "ℹ Teléfono no proporcionado (opcional)"
fi
echo "=== TODOS LOS DATOS SON VÁLIDOS ==="
return 0
}
# Ejecutar si se llama directamente
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi
./basica.sh "Juan Pérez" 30 "1234567890"
./basica.sh "" 25
./basica.sh "Ana" abc
#!/bin/bash
# Validaciones avanzadas usando expresiones regulares
# Validar formato de email
validar_email() {
local email="$1"
local patron='^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if [[ ! "$email" =~ $patron ]]; then
echo "Error: Formato de email inválido: $email" >&2
echo "Formato esperado: [email protected]" >&2
return 1
fi
return 0
}
# Validar URL
validar_url() {
local url="$1"
local patron='^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$'
if [[ ! "$url" =~ $patron ]]; then
echo "Error: Formato de URL inválido: $url" >&2
echo "Formato esperado: http://ejemplo.com o https://ejemplo.com/ruta" >&2
return 1
fi
return 0
}
# Validar dirección IP
validar_ip() {
local ip="$1"
local patron='^([0-9]{1,3}\.){3}[0-9]{1,3}$'
if [[ ! "$ip" =~ $patron ]]; then
echo "Error: Formato de IP inválido: $ip" >&2
return 1
fi
# Validar rangos (0-255)
IFS='.' read -ra OCTETOS <<< "$ip"
for octeto in "${OCTETOS[@]}"; do
if [ "$octeto" -gt 255 ] || [ "$octeto" -lt 0 ]; then
echo "Error: Octeto inválido en IP: $octeto (debe estar entre 0-255)" >&2
return 1
fi
done
return 0
}
# Validar número de teléfono (formato internacional)
validar_telefono_internacional() {
local telefono="$1"
local patron='^\+[1-9][0-9]{1,3}[0-9]{4,14}$'
if [[ ! "$telefono" =~ $patron ]]; then
echo "Error: Formato de teléfono inválido: $telefono" >&2
echo "Formato esperado: +[código país][número] (ej: +1234567890)" >&2
return 1
fi
return 0
}
# Validar contraseña segura
validar_password_segura() {
local password="$1"
local errores=0
# Al menos 8 caracteres
if [ ${#password} -lt 8 ]; then
echo "Error: La contraseña debe tener al menos 8 caracteres" >&2
((errores++))
fi
# Al menos una mayúscula
if [[ ! "$password" =~ [A-Z] ]]; then
echo "Error: La contraseña debe tener al menos una mayúscula" >&2
((errores++))
fi
# Al menos una minúscula
if [[ ! "$password" =~ [a-z] ]]; then
echo "Error: La contraseña debe tener al menos una minúscula" >&2
((errores++))
fi
# Al menos un número
if [[ ! "$password" =~ [0-9] ]]; then
echo "Error: La contraseña debe tener al menos un número" >&2
((errores++))
fi
# Al menos un carácter especial
if [[ ! "$password" =~ [^a-zA-Z0-9] ]]; then
echo "Error: La contraseña debe tener al menos un carácter especial" >&2
((errores++))
fi
return $errores
}
# Validar código postal (formato español)
validar_codigo_postal_es() {
local cp="$1"
local patron='^[0-5][0-9]{4}$'
if [[ ! "$cp" =~ $patron ]]; then
echo "Error: Código postal inválido: $cp" >&2
echo "Formato esperado: 5 dígitos (00000-59999)" >&2
return 1
fi
return 0
}
# Validar fecha (formato YYYY-MM-DD)
validar_fecha() {
local fecha="$1"
local patron='^[0-9]{4}-[0-9]{2}-[0-9]{2}$'
if [[ ! "$fecha" =~ $patron ]]; then
echo "Error: Formato de fecha inválido: $fecha" >&2
echo "Formato esperado: YYYY-MM-DD" >&2
return 1
fi
# Validar usando date
if ! date -d "$fecha" &>/dev/null; then
echo "Error: Fecha no válida: $fecha" >&2
return 1
fi
return 0
}
# Función de demostración
demo_validaciones() {
echo "=== DEMO DE VALIDACIONES CON REGEX ==="
# Emails
echo -e "\n--- EMAILS ---"
validar_email "[email protected]" && echo "✓ Email válido"
validar_email "correo_incorrecto" || echo "✗ Email inválido (esperado)"
# URLs
echo -e "\n--- URLs ---"
validar_url "https://www.ejemplo.com" && echo "✓ URL válida"
validar_url "no-es-url" || echo "✗ URL inválida (esperado)"
# IPs
echo -e "\n--- DIRECCIONES IP ---"
validar_ip "192.168.1.1" && echo "✓ IP válida"
validar_ip "999.999.999.999" || echo "✗ IP inválida (esperado)"
# Teléfonos
echo -e "\n--- TELÉFONOS ---"
validar_telefono_internacional "+1234567890" && echo "✓ Teléfono válido"
validar_telefono_internacional "123456" || echo "✗ Teléfono inválido (esperado)"
# Contraseñas
echo -e "\n--- CONTRASEÑAS ---"
validar_password_segura "MiPass123!" && echo "✓ Contraseña segura"
validar_password_segura "123" || echo "✗ Contraseña insegura (esperado)"
# Fechas
echo -e "\n--- FECHAS ---"
validar_fecha "2024-12-25" && echo "✓ Fecha válida"
validar_fecha "2024-13-45" || echo "✗ Fecha inválida (esperado)"
}
# Ejecutar demo si se llama sin argumentos
if [ $# -eq 0 ]; then
demo_validaciones
fi
#!/bin/bash
# Validaciones para archivos y directorios
# Validar que el archivo existe
validar_archivo_existe() {
local archivo="$1"
local nombre="${2:-archivo}"
if [ ! -f "$archivo" ]; then
echo "Error: El $nombre '$archivo' no existe" >&2
return 1
fi
return 0
}
# Validar que el archivo es legible
validar_archivo_legible() {
local archivo="$1"
local nombre="${2:-archivo}"
if [ ! -r "$archivo" ]; then
echo "Error: No se puede leer el $nombre '$archivo'" >&2
return 1
fi
return 0
}
# Validar que el archivo es escribible
validar_archivo_escribible() {
local archivo="$1"
local nombre="${2:-archivo}"
if [ ! -w "$archivo" ]; then
echo "Error: No se puede escribir en el $nombre '$archivo'" >&2
return 1
fi
return 0
}
# Validar que el archivo es ejecutable
validar_archivo_ejecutable() {
local archivo="$1"
local nombre="${2:-archivo}"
if [ ! -x "$archivo" ]; then
echo "Error: El $nombre '$archivo' no es ejecutable" >&2
return 1
fi
return 0
}
# Validar tamaño de archivo
validar_tamaño_archivo() {
local archivo="$1"
local tamaño_max="$2" # en bytes
local nombre="${3:-archivo}"
if [ ! -f "$archivo" ]; then
echo "Error: El $nombre '$archivo' no existe" >&2
return 1
fi
local tamaño_actual=$(stat -f%z "$archivo" 2>/dev/null || stat -c%s "$archivo" 2>/dev/null)
if [ "$tamaño_actual" -gt "$tamaño_max" ]; then
echo "Error: El $nombre '$archivo' es demasiado grande ($(($tamaño_actual/1024))KB > $(($tamaño_max/1024))KB)" >&2
return 1
fi
return 0
}
# Validar extensión de archivo
validar_extension() {
local archivo="$1"
local extension_esperada="$2"
local extension_real="${archivo##*.}"
if [ "$extension_real" != "$extension_esperada" ]; then
echo "Error: El archivo debe tener extensión .$extension_esperada (encontrada: .$extension_real)" >&2
return 1
fi
return 0
}
# Validar tipo MIME
validar_tipo_mime() {
local archivo="$1"
local tipo_esperado="$2"
if ! command -v file &> /dev/null; then
echo "Error: El comando 'file' no está disponible" >&2
return 1
fi
local tipo_real=$(file -b --mime-type "$archivo" 2>/dev/null)
if [ "$tipo_real" != "$tipo_esperado" ]; then
echo "Error: Tipo de archivo incorrecto (esperado: $tipo_esperado, encontrado: $tipo_real)" >&2
return 1
fi
return 0
}
# Validar directorio
validar_directorio() {
local directorio="$1"
local nombre="${2:-directorio}"
if [ ! -d "$directorio" ]; then
echo "Error: El $nombre '$directorio' no existe o no es un directorio" >&2
return 1
fi
return 0
}
# Validar directorio escribible
validar_directorio_escribible() {
local directorio="$1"
local nombre="${2:-directorio}"
if ! validar_directorio "$directorio" "$nombre"; then
return 1
fi
if [ ! -w "$directorio" ]; then
echo "Error: No se puede escribir en el $nombre '$directorio'" >&2
return 1
fi
return 0
}
# Validar espacio libre en directorio
validar_espacio_libre() {
local directorio="$1"
local espacio_min="$2" # en KB
local espacio_libre=$(df "$directorio" | awk 'NR==2 {print $4}')
if [ "$espacio_libre" -lt "$espacio_min" ]; then
echo "Error: No hay suficiente espacio libre en '$directorio'" >&2
echo "Requerido: ${espacio_min}KB, disponible: ${espacio_libre}KB" >&2
return 1
fi
return 0
}
# Función integral para validar archivo de entrada
validar_archivo_entrada() {
local archivo="$1"
local max_size="${2:-1048576}" # 1MB por defecto
echo "Validando archivo de entrada: $archivo"
# Validaciones en cadena
if validar_archivo_existe "$archivo" && \
validar_archivo_legible "$archivo" && \
validar_tamaño_archivo "$archivo" "$max_size"; then
echo "✓ Archivo de entrada válido"
return 0
else
echo "✗ Archivo de entrada inválido"
return 1
fi
}
# Función integral para validar directorio de salida
validar_directorio_salida() {
local directorio="$1"
local espacio_min="${2:-10240}" # 10MB por defecto
echo "Validando directorio de salida: $directorio"
if validar_directorio "$directorio" "directorio de salida" && \
validar_directorio_escribible "$directorio" "directorio de salida" && \
validar_espacio_libre "$directorio" "$espacio_min"; then
echo "✓ Directorio de salida válido"
return 0
else
echo "✗ Directorio de salida inválido"
return 1
fi
}
# Ejemplo de uso
main() {
if [ $# -lt 2 ]; then
echo "Uso: $0 archivo_entrada directorio_salida"
exit 1
fi
local archivo_entrada="$1"
local directorio_salida="$2"
echo "=== VALIDACIÓN DE ARCHIVOS Y DIRECTORIOS ==="
# Validar archivo de entrada
if ! validar_archivo_entrada "$archivo_entrada"; then
exit 1
fi
echo ""
# Validar directorio de salida
if ! validar_directorio_salida "$directorio_salida"; then
exit 1
fi
echo ""
echo "✓ Todas las validaciones pasaron correctamente"
echo "Listo para procesar '$archivo_entrada' hacia '$directorio_salida'"
}
# Ejecutar si se llama directamente
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi
#!/bin/bash
# Framework de validación robusto para Bash
# Variables globales
declare -g VALIDATION_ERRORS=()
declare -g VALIDATION_WARNINGS=()
declare -g VALIDATION_STRICT=true
# Limpiar errores y advertencias
validation_reset() {
VALIDATION_ERRORS=()
VALIDATION_WARNINGS=()
}
# Agregar error de validación
validation_add_error() {
local mensaje="$1"
VALIDATION_ERRORS+=("ERROR: $mensaje")
}
# Agregar advertencia de validación
validation_add_warning() {
local mensaje="$1"
VALIDATION_WARNINGS+=("WARN: $mensaje")
}
# Verificar si hay errores
validation_has_errors() {
[ ${#VALIDATION_ERRORS[@]} -gt 0 ]
}
# Verificar si hay advertencias
validation_has_warnings() {
[ ${#VALIDATION_WARNINGS[@]} -gt 0 ]
}
# Mostrar todos los errores y advertencias
validation_show_results() {
local show_warnings="${1:-true}"
if validation_has_errors; then
echo "❌ ERRORES DE VALIDACIÓN:" >&2
printf '%s\n' "${VALIDATION_ERRORS[@]}" >&2
echo "" >&2
fi
if [ "$show_warnings" = true ] && validation_has_warnings; then
echo "⚠️ ADVERTENCIAS DE VALIDACIÓN:" >&2
printf '%s\n' "${VALIDATION_WARNINGS[@]}" >&2
echo "" >&2
fi
}
# Validador genérico
validate() {
local valor="$1"
local tipo="$2"
local nombre="$3"
local opciones="$4"
case "$tipo" in
"required")
if [ -z "$valor" ]; then
validation_add_error "$nombre es requerido"
return 1
fi
;;
"email")
if [[ ! "$valor" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
validation_add_error "$nombre debe ser un email válido"
return 1
fi
;;
"url")
if [[ ! "$valor" =~ ^https?://[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}(/.*)?$ ]]; then
validation_add_error "$nombre debe ser una URL válida"
return 1
fi
;;
"ip")
if ! validate_ip_address "$valor"; then
validation_add_error "$nombre debe ser una dirección IP válida"
return 1
fi
;;
"number")
if [[ ! "$valor" =~ ^-?[0-9]+$ ]]; then
validation_add_error "$nombre debe ser un número entero"
return 1
fi
;;
"positive")
validate "$valor" "number" "$nombre"
if [ $? -eq 0 ] && [ "$valor" -le 0 ]; then
validation_add_error "$nombre debe ser un número positivo"
return 1
fi
;;
"range")
validate "$valor" "number" "$nombre"
if [ $? -eq 0 ]; then
IFS='-' read -ra RANGE <<< "$opciones"
local min="${RANGE[0]}"
local max="${RANGE[1]}"
if [ "$valor" -lt "$min" ] || [ "$valor" -gt "$max" ]; then
validation_add_error "$nombre debe estar entre $min y $max"
return 1
fi
fi
;;
"length")
IFS='-' read -ra RANGE <<< "$opciones"
local min="${RANGE[0]:-0}"
local max="${RANGE[1]:-999999}"
if [ ${#valor} -lt "$min" ] || [ ${#valor} -gt "$max" ]; then
validation_add_error "$nombre debe tener entre $min y $max caracteres"
return 1
fi
;;
"file")
if [ ! -f "$valor" ]; then
validation_add_error "$nombre debe ser un archivo existente"
return 1
fi
;;
"dir")
if [ ! -d "$valor" ]; then
validation_add_error "$nombre debe ser un directorio existente"
return 1
fi
;;
"readable")
if [ ! -r "$valor" ]; then
validation_add_error "$nombre debe ser legible"
return 1
fi
;;
"writable")
if [ ! -w "$valor" ]; then
validation_add_error "$nombre debe ser escribible"
return 1
fi
;;
"executable")
if [ ! -x "$valor" ]; then
validation_add_error "$nombre debe ser ejecutable"
return 1
fi
;;
*)
validation_add_error "Tipo de validación desconocido: $tipo"
return 1
;;
esac
return 0
}
# Función auxiliar para validar IP
validate_ip_address() {
local ip="$1"
if [[ ! "$ip" =~ ^([0-9]{1,3}\.){3}[0-9]{1,3}$ ]]; then
return 1
fi
IFS='.' read -ra OCTETS <<< "$ip"
for octet in "${OCTETS[@]}"; do
if [ "$octet" -gt 255 ] || [ "$octet" -lt 0 ]; then
return 1
fi
done
return 0
}
# Validador de esquemas complejos
validate_schema() {
local -n schema_ref=$1
local -n data_ref=$2
validation_reset
for field in "${!schema_ref[@]}"; do
local rules="${schema_ref[$field]}"
local value="${data_ref[$field]}"
# Procesar múltiples reglas separadas por |
IFS='|' read -ra RULES <<< "$rules"
for rule in "${RULES[@]}"; do
IFS=':' read -ra RULE_PARTS <<< "$rule"
local rule_type="${RULE_PARTS[0]}"
local rule_option="${RULE_PARTS[1]:-}"
validate "$value" "$rule_type" "$field" "$rule_option"
done
done
}
# Ejemplo de uso del framework
demo_validation_framework() {
echo "=== DEMO DEL FRAMEWORK DE VALIDACIÓN ==="
# Definir esquema de validación
declare -A user_schema=(
["name"]="required|length:2-50"
["email"]="required|email"
["age"]="required|positive|range:18-120"
["website"]="url"
["config_file"]="file|readable"
["output_dir"]="dir|writable"
)
# Datos de ejemplo (algunos inválidos intencionalmente)
declare -A user_data=(
["name"]="Juan Pérez"
["email"]="[email protected]"
["age"]="25"
["website"]="https://ejemplo.com"
["config_file"]="/etc/passwd" # Existe pero puede no ser legible
["output_dir"]="/tmp"
)
echo "Validando datos del usuario..."
validate_schema user_schema user_data
if validation_has_errors; then
echo "❌ Validación fallida:"
validation_show_results
return 1
else
echo "✅ Validación exitosa:"
if validation_has_warnings; then
validation_show_results
else
echo "No se encontraron errores ni advertencias"
fi
return 0
fi
}
# Función principal para demostración
main() {
if [ $# -eq 0 ]; then
demo_validation_framework
else
echo "Framework de validación cargado"
echo "Use las funciones validate() y validate_schema() en sus scripts"
fi
}
# Ejecutar si se llama directamente
if [ "${BASH_SOURCE[0]}" = "${0}" ]; then
main "$@"
fi
Crea un script que valide un archivo de configuración:
Desarrolla un script de registro que: