Volver
Módulo 8 - Administración del Sistema AVANZADO

Programación de Tareas (Cron)

Automatiza tareas del sistema con cron y systemd para administración eficiente

La programación de tareas es esencial para la administración automatizada de sistemas. Cron permite ejecutar scripts y comandos en horarios específicos, mientras que systemd ofrece capacidades avanzadas de scheduling y gestión de servicios.

Formato de Crontab

Estructura de una entrada cron:

MINUTO
0-59
HORA
0-23
DÍA
1-31
MES
1-12
DÍA SEMANA
0-7 (0=Dom)
COMANDO
Script/comando

Formato: minuto hora día mes día_semana comando

Ejemplos Comunes de Cron

Expresión Cron Descripción Frecuencia
0 2 * * * Diariamente a las 2:00 AM Una vez al día
*/5 * * * * Cada 5 minutos 12 veces por hora
0 0 * * 1 Lunes a medianoche Una vez por semana
30 14 1 * * Primer día del mes a las 2:30 PM Una vez por mes
0 */4 * * * Cada 4 horas 6 veces al día
0 9-17 * * 1-5 Cada hora de 9AM a 5PM, lunes a viernes Horario laboral
@reboot Al iniciar el sistema Una vez por reinicio
@daily Equivalente a 0 0 * * * Una vez al día

Gestor Avanzado de Crontab

Gestor de crontab - cron_manager.sh
bash
#!/bin/bash

# Gestor avanzado de crontab con respaldos y validación
# Versión: 2.0

set -euo pipefail

# Configuración
declare -g BACKUP_DIR="/var/backups/crontab"
declare -g LOG_FILE="/var/log/cron_manager.log"
declare -g CONFIG_DIR="$HOME/.cron_manager"
declare -g TEMPLATES_DIR="$CONFIG_DIR/templates"

# Crear directorios necesarios
mkdir -p "$BACKUP_DIR" "$CONFIG_DIR" "$TEMPLATES_DIR"

# Función de logging
log() {
    local message="[$(date '+%Y-%m-%d %H:%M:%S')] $*"
    echo "$message" | tee -a "$LOG_FILE"
}

# Crear backup del crontab actual
backup_crontab() {
    local user="${1:-$(whoami)}"
    local backup_file="$BACKUP_DIR/crontab_${user}_$(date +%Y%m%d_%H%M%S).bak"
    
    log "Creando backup del crontab de $user"
    
    if crontab -l -u "$user" >/dev/null 2>&1; then
        crontab -l -u "$user" > "$backup_file"
        log "Backup creado: $backup_file"
        echo "$backup_file"
    else
        log "No hay crontab existente para el usuario $user"
        touch "$backup_file"
        echo "$backup_file"
    fi
}

# Validar expresión cron
validate_cron_expression() {
    local expression="$1"
    
    # Separar la expresión en campos
    IFS=' ' read -ra FIELDS <<< "$expression"
    
    # Verificar que tenga 5 o 6 campos (6 si incluye segundos en algunas implementaciones)
    if [ ${#FIELDS[@]} -lt 5 ]; then
        echo "Error: Expresión cron incompleta. Se requieren al menos 5 campos."
        return 1
    fi
    
    # Extraer campos
    local minute="${FIELDS[0]}"
    local hour="${FIELDS[1]}"
    local day="${FIELDS[2]}"
    local month="${FIELDS[3]}"
    local weekday="${FIELDS[4]}"
    
    # Función auxiliar para validar rangos
    validate_field() {
        local field="$1"
        local min_val="$2"
        local max_val="$3"
        local field_name="$4"
        
        # Permitir asterisco y expresiones especiales
        if [[ "$field" == "*" ]] || [[ "$field" =~ ^@(reboot|yearly|annually|monthly|weekly|daily|hourly)$ ]]; then
            return 0
        fi
        
        # Validar rangos con */step
        if [[ "$field" =~ ^\*/[0-9]+$ ]]; then
            local step=${field#*/}
            if [ "$step" -gt "$max_val" ]; then
                echo "Error: Paso demasiado grande para $field_name: $step"
                return 1
            fi
            return 0
        fi
        
        # Validar rangos simples
        if [[ "$field" =~ ^[0-9]+$ ]]; then
            if [ "$field" -lt "$min_val" ] || [ "$field" -gt "$max_val" ]; then
                echo "Error: Valor fuera de rango para $field_name: $field (rango: $min_val-$max_val)"
                return 1
            fi
            return 0
        fi
        
        # Validar listas (ej: 1,3,5)
        if [[ "$field" =~ ^[0-9,]+$ ]]; then
            IFS=',' read -ra VALUES <<< "$field"
            for value in "${VALUES[@]}"; do
                if [ "$value" -lt "$min_val" ] || [ "$value" -gt "$max_val" ]; then
                    echo "Error: Valor fuera de rango en $field_name: $value"
                    return 1
                fi
            done
            return 0
        fi
        
        # Validar rangos (ej: 9-17)
        if [[ "$field" =~ ^[0-9]+-[0-9]+$ ]]; then
            local start=${field%-*}
            local end=${field#*-}
            if [ "$start" -lt "$min_val" ] || [ "$start" -gt "$max_val" ] || \
               [ "$end" -lt "$min_val" ] || [ "$end" -gt "$max_val" ] || \
               [ "$start" -gt "$end" ]; then
                echo "Error: Rango inválido para $field_name: $field"
                return 1
            fi
            return 0
        fi
        
        echo "Error: Formato inválido para $field_name: $field"
        return 1
    }
    
    # Manejar expresiones especiales
    if [[ "$minute" =~ ^@(reboot|yearly|annually|monthly|weekly|daily|hourly)$ ]]; then
        return 0
    fi
    
    # Validar cada campo
    validate_field "$minute" 0 59 "minuto" || return 1
    validate_field "$hour" 0 23 "hora" || return 1
    validate_field "$day" 1 31 "día" || return 1
    validate_field "$month" 1 12 "mes" || return 1
    validate_field "$weekday" 0 7 "día de la semana" || return 1
    
    return 0
}

# Agregar tarea cron
add_cron_job() {
    local user="${1:-$(whoami)}"
    local schedule="$2"
    local command="$3"
    local description="${4:-Tarea automatizada}"
    
    log "Agregando tarea cron para usuario $user"
    log "Horario: $schedule"
    log "Comando: $command"
    
    # Validar expresión cron
    if ! validate_cron_expression "$schedule"; then
        return 1
    fi
    
    # Crear backup
    local backup_file
    backup_file=$(backup_crontab "$user")
    
    # Obtener crontab actual
    local current_crontab=""
    if crontab -l -u "$user" >/dev/null 2>&1; then
        current_crontab=$(crontab -l -u "$user")
    fi
    
    # Agregar nueva tarea con comentario
    local new_entry="# $description - Agregado $(date)
$schedule $command"
    
    # Verificar si la tarea ya existe
    if echo "$current_crontab" | grep -Fq "$schedule $command"; then
        log "Advertencia: La tarea ya existe en el crontab"
        return 2
    fi
    
    # Crear nuevo crontab
    local new_crontab=""
    if [ -n "$current_crontab" ]; then
        new_crontab="$current_crontab
$new_entry"
    else
        new_crontab="$new_entry"
    fi
    
    # Aplicar nuevo crontab
    echo "$new_crontab" | crontab -u "$user" -
    
    if [ $? -eq 0 ]; then
        log "✓ Tarea agregada exitosamente"
        return 0
    else
        log "✗ Error al agregar tarea, restaurando backup"
        if [ -s "$backup_file" ]; then
            crontab -u "$user" "$backup_file"
        fi
        return 1
    fi
}

# Eliminar tarea cron
remove_cron_job() {
    local user="${1:-$(whoami)}"
    local pattern="$2"
    
    log "Eliminando tareas que coincidan con: $pattern"
    
    # Crear backup
    local backup_file
    backup_file=$(backup_crontab "$user")
    
    # Obtener crontab actual
    if ! crontab -l -u "$user" >/dev/null 2>&1; then
        log "No hay crontab para el usuario $user"
        return 1
    fi
    
    local current_crontab
    current_crontab=$(crontab -l -u "$user")
    
    # Filtrar líneas que no coincidan con el patrón
    local new_crontab
    new_crontab=$(echo "$current_crontab" | grep -v "$pattern")
    
    # Verificar si se eliminó algo
    if [ "$current_crontab" = "$new_crontab" ]; then
        log "No se encontraron tareas que coincidan con el patrón"
        return 2
    fi
    
    # Aplicar nuevo crontab
    if [ -n "$new_crontab" ]; then
        echo "$new_crontab" | crontab -u "$user" -
    else
        crontab -r -u "$user"  # Eliminar todo el crontab si está vacío
    fi
    
    if [ $? -eq 0 ]; then
        log "✓ Tareas eliminadas exitosamente"
        return 0
    else
        log "✗ Error al eliminar tareas, restaurando backup"
        crontab -u "$user" "$backup_file"
        return 1
    fi
}

# Listar tareas cron con formato mejorado
list_cron_jobs() {
    local user="${1:-$(whoami)}"
    
    echo "=== TAREAS CRON PARA $user ==="
    echo ""
    
    if ! crontab -l -u "$user" >/dev/null 2>&1; then
        echo "No hay tareas programadas para el usuario $user"
        return 1
    fi
    
    local crontab_content
    crontab_content=$(crontab -l -u "$user")
    
    echo "$crontab_content" | while IFS= read -r line; do
        if [[ "$line" =~ ^#.* ]]; then
            # Línea de comentario
            echo -e "\033[36m$line\033[0m"
        elif [[ -n "$line" ]]; then
            # Línea de tarea cron
            echo -e "\033[32m$line\033[0m"
            
            # Intentar explicar la programación
            local schedule=$(echo "$line" | awk '{print $1" "$2" "$3" "$4" "$5}')
            local explanation
            explanation=$(explain_cron_schedule "$schedule")
            if [ -n "$explanation" ]; then
                echo -e "\033[90m  → $explanation\033[0m"
            fi
            echo ""
        fi
    done
}

# Explicar horario cron en lenguaje natural
explain_cron_schedule() {
    local schedule="$1"
    IFS=' ' read -ra FIELDS <<< "$schedule"
    
    if [ ${#FIELDS[@]} -ne 5 ]; then
        return 1
    fi
    
    local minute="${FIELDS[0]}"
    local hour="${FIELDS[1]}"
    local day="${FIELDS[2]}"
    local month="${FIELDS[3]}"
    local weekday="${FIELDS[4]}"
    
    local explanation=""
    
    # Casos especiales
    case "$schedule" in
        "0 0 * * *") explanation="Diariamente a medianoche" ;;
        "0 2 * * *") explanation="Diariamente a las 2:00 AM" ;;
        "*/5 * * * *") explanation="Cada 5 minutos" ;;
        "*/15 * * * *") explanation="Cada 15 minutos" ;;
        "*/30 * * * *") explanation="Cada 30 minutos" ;;
        "0 * * * *") explanation="Cada hora" ;;
        "0 0 * * 0") explanation="Semanalmente los domingos a medianoche" ;;
        "0 0 1 * *") explanation="Mensualmente el día 1 a medianoche" ;;
        *)
            # Construir explicación dinámica
            local freq_parts=()
            
            # Minuto
            case "$minute" in
                "*") ;;
                "0") freq_parts+=("a las :00") ;;
                "*/5") freq_parts+=("cada 5 minutos") ;;
                "*/15") freq_parts+=("cada 15 minutos") ;;
                "*/30") freq_parts+=("cada 30 minutos") ;;
                [0-9]*) freq_parts+=("en el minuto $minute") ;;
            esac
            
            # Hora
            case "$hour" in
                "*") ;;
                "*/2") freq_parts+=("cada 2 horas") ;;
                "*/4") freq_parts+=("cada 4 horas") ;;
                [0-9]*) freq_parts+=("a las ${hour}:00") ;;
            esac
            
            # Día del mes
            case "$day" in
                "*") ;;
                "1") freq_parts+=("el día 1 del mes") ;;
                [0-9]*) freq_parts+=("el día $day del mes") ;;
            esac
            
            # Día de la semana
            case "$weekday" in
                "*") ;;
                "0"|"7") freq_parts+=("los domingos") ;;
                "1") freq_parts+=("los lunes") ;;
                "2") freq_parts+=("los martes") ;;
                "3") freq_parts+=("los miércoles") ;;
                "4") freq_parts+=("los jueves") ;;
                "5") freq_parts+=("los viernes") ;;
                "6") freq_parts+=("los sábados") ;;
                "1-5") freq_parts+=("de lunes a viernes") ;;
            esac
            
            if [ ${#freq_parts[@]} -gt 0 ]; then
                explanation=$(IFS=", "; echo "${freq_parts[*]}")
                explanation="Se ejecuta $explanation"
            fi
            ;;
    esac
    
    echo "$explanation"
}

# Crear plantillas de tareas comunes
create_templates() {
    local templates=(
        "backup_daily:0 2 * * *:/usr/local/bin/backup_script.sh:Backup diario"
        "backup_weekly:0 3 * * 1:/usr/local/bin/backup_weekly.sh:Backup semanal"
        "cleanup_logs:0 4 * * *:/usr/local/bin/cleanup_logs.sh:Limpieza diaria de logs"
        "update_system:0 5 * * 1:/usr/bin/apt update && /usr/bin/apt upgrade -y:Actualización semanal del sistema"
        "monitor_disk:*/30 * * * *:/usr/local/bin/check_disk_space.sh:Monitoreo de espacio en disco"
        "restart_service:0 6 * * *:systemctl restart nginx:Reiniciar nginx diariamente"
        "generate_report:0 8 1 * *:/usr/local/bin/monthly_report.sh:Reporte mensual"
        "check_ssl:0 9 * * 1:/usr/local/bin/check_ssl_certs.sh:Verificar certificados SSL"
    )
    
    log "Creando plantillas de tareas cron"
    
    for template in "${templates[@]}"; do
        IFS=':' read -ra TEMPLATE_PARTS <<< "$template"
        local name="${TEMPLATE_PARTS[0]}"
        local schedule="${TEMPLATE_PARTS[1]}"
        local command="${TEMPLATE_PARTS[2]}"
        local description="${TEMPLATE_PARTS[3]}"
        
        cat > "$TEMPLATES_DIR/$name.template" << EOF
# Plantilla: $name
# Descripción: $description
# Programación: $schedule
# Comando: $command

$schedule $command
EOF
    done
    
    log "Plantillas creadas en $TEMPLATES_DIR"
}

# Aplicar plantilla
apply_template() {
    local user="${1:-$(whoami)}"
    local template_name="$2"
    local template_file="$TEMPLATES_DIR/$template_name.template"
    
    if [ ! -f "$template_file" ]; then
        log "Error: Plantilla no encontrada: $template_name"
        return 1
    fi
    
    log "Aplicando plantilla: $template_name"
    
    # Leer plantilla (ignorar líneas de comentario)
    local cron_line
    cron_line=$(grep -v '^#' "$template_file" | head -1)
    
    if [ -n "$cron_line" ]; then
        # Extraer partes de la línea cron
        local schedule command
        schedule=$(echo "$cron_line" | awk '{print $1" "$2" "$3" "$4" "$5}')
        command=$(echo "$cron_line" | cut -d' ' -f6-)
        
        # Obtener descripción del template
        local description
        description=$(grep "^# Descripción:" "$template_file" | cut -d' ' -f3-)
        
        add_cron_job "$user" "$schedule" "$command" "$description"
    else
        log "Error: Plantilla vacía o inválida"
        return 1
    fi
}

# Monitorear ejecución de cron
monitor_cron_execution() {
    local duration="${1:-300}"  # 5 minutos por defecto
    
    log "Monitoreando ejecuciones de cron por $duration segundos"
    
    # Seguir el log de cron
    timeout "$duration" tail -f /var/log/cron 2>/dev/null || \
    timeout "$duration" tail -f /var/log/syslog | grep CRON 2>/dev/null || \
    {
        echo "No se pudo acceder a los logs de cron"
        echo "Intente con: sudo journalctl -f -u cron"
    }
}

# Generar reporte de tareas cron
generate_cron_report() {
    local output_file="${1:-/tmp/cron_report_$(date +%Y%m%d_%H%M%S).html}"
    
    log "Generando reporte de tareas cron: $output_file"
    
    cat > "$output_file" << 'EOF'




    
    Reporte de Tareas Cron
    


    

Reporte de Tareas Cron

Generado el: $(date)

Servidor: $(hostname)

EOF # Obtener todos los usuarios con crontab local users=() # Usuarios del sistema for user in $(cut -f1 -d: /etc/passwd); do if crontab -l -u "$user" >/dev/null 2>&1; then users+=("$user") fi done # Usuario actual si no está en la lista if [[ ! " ${users[*]} " =~ " $(whoami) " ]] && crontab -l >/dev/null 2>&1; then users+=("$(whoami)") fi echo "

Usuarios con Tareas Programadas: ${#users[@]}

" >> "$output_file" for user in "${users[@]}"; do echo "
" >> "$output_file" echo "

Usuario: $user

" >> "$output_file" local crontab_content crontab_content=$(crontab -l -u "$user" 2>/dev/null) echo "" >> "$output_file" echo "" >> "$output_file" echo "$crontab_content" | while IFS= read -r line; do if [[ ! "$line" =~ ^#.* ]] && [[ -n "$line" ]]; then local schedule command explanation schedule=$(echo "$line" | awk '{print $1" "$2" "$3" "$4" "$5}') command=$(echo "$line" | cut -d' ' -f6-) explanation=$(explain_cron_schedule "$schedule") echo "" >> "$output_file" echo "" >> "$output_file" echo "" >> "$output_file" echo "" >> "$output_file" echo "" >> "$output_file" fi done echo "
ProgramaciónComandoExplicación
$schedule$command${explanation:-'N/A'}
" >> "$output_file" echo "
" >> "$output_file" done echo "" >> "$output_file" log "Reporte generado: $output_file" echo "$output_file" } # Función de ayuda show_help() { cat << EOF Gestor Avanzado de Crontab v2.0 USO: $0 COMANDO [OPCIONES] COMANDOS: add USER SCHEDULE COMMAND [DESCRIPTION] Agregar nueva tarea cron remove USER PATTERN Eliminar tareas que coincidan con el patrón list [USER] Listar tareas del usuario validate "CRON_EXPRESSION" Validar expresión cron explain "CRON_EXPRESSION" Explicar horario en lenguaje natural backup [USER] Crear backup del crontab templates Crear plantillas de tareas comunes apply USER TEMPLATE_NAME Aplicar plantilla monitor [SECONDS] Monitorear ejecuciones de cron report [OUTPUT_FILE] Generar reporte HTML help Mostrar esta ayuda EJEMPLOS: # Agregar backup diario $0 add root "0 2 * * *" "/usr/local/bin/backup.sh" "Backup diario" # Listar tareas del usuario actual $0 list # Validar expresión $0 validate "*/15 * * * *" # Aplicar plantilla $0 apply root backup_daily # Generar reporte $0 report /tmp/cron_report.html ARCHIVOS: Backups: $BACKUP_DIR Plantillas: $TEMPLATES_DIR Log: $LOG_FILE EOF } # Función principal main() { case "${1:-help}" in add) if [ $# -lt 4 ]; then echo "Error: Argumentos insuficientes para 'add'" echo "Uso: $0 add USER SCHEDULE COMMAND [DESCRIPTION]" exit 1 fi add_cron_job "$2" "$3" "$4" "${5:-Tarea automatizada}" ;; remove) if [ $# -lt 3 ]; then echo "Error: Argumentos insuficientes para 'remove'" echo "Uso: $0 remove USER PATTERN" exit 1 fi remove_cron_job "$2" "$3" ;; list) list_cron_jobs "${2:-$(whoami)}" ;; validate) if [ $# -lt 2 ]; then echo "Error: Se requiere expresión cron" echo "Uso: $0 validate \"CRON_EXPRESSION\"" exit 1 fi if validate_cron_expression "$2"; then echo "✓ Expresión cron válida: $2" else exit 1 fi ;; explain) if [ $# -lt 2 ]; then echo "Error: Se requiere expresión cron" echo "Uso: $0 explain \"CRON_EXPRESSION\"" exit 1 fi local explanation explanation=$(explain_cron_schedule "$2") echo "Expresión: $2" echo "Explicación: ${explanation:-'No se pudo explicar esta expresión'}" ;; backup) backup_crontab "${2:-$(whoami)}" ;; templates) create_templates ;; apply) if [ $# -lt 3 ]; then echo "Error: Argumentos insuficientes para 'apply'" echo "Uso: $0 apply USER TEMPLATE_NAME" exit 1 fi apply_template "$2" "$3" ;; monitor) monitor_cron_execution "${2:-300}" ;; report) generate_cron_report "${2:-}" ;; help|--help|-h) show_help ;; *) echo "Comando no válido: $1" show_help exit 1 ;; esac } # Verificar si se ejecuta como script principal if [ "${BASH_SOURCE[0]}" = "${0}" ]; then main "$@" fi
./cron_manager.sh add root "0 2 * * *" "/usr/local/bin/backup.sh" "Backup diario"
./cron_manager.sh list
./cron_manager.sh validate "*/15 * * * *"

Systemd Timers (Alternativa Moderna)

Creador de systemd timers - systemd_timer_creator.sh
bash
#!/bin/bash

# Creador de systemd timers
# Alternativa moderna a cron

set -euo pipefail

# Configuración
SYSTEMD_USER_DIR="$HOME/.config/systemd/user"
SYSTEMD_SYSTEM_DIR="/etc/systemd/system"

# Función de logging
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"
}

# Crear servicio systemd
create_systemd_service() {
    local service_name="$1"
    local description="$2"
    local command="$3"
    local working_dir="${4:-/tmp}"
    local user="${5:-$USER}"
    local is_system="${6:-false}"
    
    local service_file
    if [ "$is_system" = "true" ]; then
        service_file="$SYSTEMD_SYSTEM_DIR/$service_name.service"
        mkdir -p "$SYSTEMD_SYSTEM_DIR"
    else
        service_file="$SYSTEMD_USER_DIR/$service_name.service"
        mkdir -p "$SYSTEMD_USER_DIR"
    fi
    
    log "Creando servicio: $service_file"
    
    cat > "$service_file" << EOF
[Unit]
Description=$description
After=network.target

[Service]
Type=oneshot
User=$user
WorkingDirectory=$working_dir
ExecStart=$command
StandardOutput=journal
StandardError=journal

[Install]
WantedBy=multi-user.target
EOF
    
    log "✓ Servicio creado: $service_name.service"
}

# Crear timer systemd
create_systemd_timer() {
    local timer_name="$1"
    local description="$2"
    local schedule="$3"
    local is_system="${4:-false}"
    
    local timer_file
    if [ "$is_system" = "true" ]; then
        timer_file="$SYSTEMD_SYSTEM_DIR/$timer_name.timer"
    else
        timer_file="$SYSTEMD_USER_DIR/$timer_name.timer"
    fi
    
    log "Creando timer: $timer_file"
    
    cat > "$timer_file" << EOF
[Unit]
Description=$description Timer
Requires=$timer_name.service

[Timer]
$schedule
Persistent=true

[Install]
WantedBy=timers.target
EOF
    
    log "✓ Timer creado: $timer_name.timer"
}

# Convertir expresión cron a systemd
cron_to_systemd() {
    local cron_expr="$1"
    local systemd_schedule=""
    
    # Separar campos de cron
    IFS=' ' read -ra FIELDS <<< "$cron_expr"
    
    if [ ${#FIELDS[@]} -ne 5 ]; then
        echo "Error: Expresión cron inválida"
        return 1
    fi
    
    local minute="${FIELDS[0]}"
    local hour="${FIELDS[1]}"
    local day="${FIELDS[2]}"
    local month="${FIELDS[3]}"
    local weekday="${FIELDS[4]}"
    
    # Casos especiales comunes
    case "$cron_expr" in
        "0 0 * * *")
            systemd_schedule="OnCalendar=daily"
            ;;
        "0 * * * *")
            systemd_schedule="OnCalendar=hourly"
            ;;
        "0 0 * * 1")
            systemd_schedule="OnCalendar=weekly"
            ;;
        "0 0 1 * *")
            systemd_schedule="OnCalendar=monthly"
            ;;
        "*/5 * * * *")
            systemd_schedule="OnCalendar=*:0/5"
            ;;
        "*/15 * * * *")
            systemd_schedule="OnCalendar=*:0/15"
            ;;
        "*/30 * * * *")
            systemd_schedule="OnCalendar=*:0/30"
            ;;
        *)
            # Construcción más compleja
            local time_spec=""
            local date_spec=""
            
            # Construir especificación de tiempo
            if [ "$minute" = "*" ] && [ "$hour" = "*" ]; then
                time_spec="*:*"
            elif [ "$minute" = "0" ] && [ "$hour" = "*" ]; then
                time_spec="*:00"
            elif [ "$minute" != "*" ] && [ "$hour" != "*" ]; then
                time_spec=$(printf "%02d:%02d" "$hour" "$minute")
            fi
            
            # Construir especificación de fecha
            if [ "$weekday" != "*" ]; then
                case "$weekday" in
                    0|7) date_spec="Sun" ;;
                    1) date_spec="Mon" ;;
                    2) date_spec="Tue" ;;
                    3) date_spec="Wed" ;;
                    4) date_spec="Thu" ;;
                    5) date_spec="Fri" ;;
                    6) date_spec="Sat" ;;
                esac
            fi
            
            if [ -n "$date_spec" ]; then
                systemd_schedule="OnCalendar=$date_spec $time_spec"
            else
                systemd_schedule="OnCalendar=$time_spec"
            fi
            ;;
    esac
    
    echo "$systemd_schedule"
}

# Crear tarea programada completa
create_scheduled_task() {
    local name="$1"
    local description="$2"
    local command="$3"
    local cron_schedule="$4"
    local working_dir="${5:-/tmp}"
    local user="${6:-$USER}"
    local is_system="${7:-false}"
    
    log "Creando tarea programada: $name"
    
    # Crear servicio
    create_systemd_service "$name" "$description" "$command" "$working_dir" "$user" "$is_system"
    
    # Convertir horario cron a systemd
    local systemd_schedule
    systemd_schedule=$(cron_to_systemd "$cron_schedule")
    
    if [ $? -ne 0 ]; then
        log "Error: No se pudo convertir el horario cron"
        return 1
    fi
    
    # Crear timer
    create_systemd_timer "$name" "$description" "$systemd_schedule" "$is_system"
    
    # Recargar systemd y habilitar timer
    if [ "$is_system" = "true" ]; then
        systemctl daemon-reload
        systemctl enable "$name.timer"
        systemctl start "$name.timer"
        log "✓ Timer del sistema habilitado e iniciado"
    else
        systemctl --user daemon-reload
        systemctl --user enable "$name.timer"
        systemctl --user start "$name.timer"
        log "✓ Timer de usuario habilitado e iniciado"
    fi
    
    log "✓ Tarea programada '$name' creada exitosamente"
}

# Listar timers activos
list_timers() {
    local scope="${1:-user}"
    
    echo "=== TIMERS SYSTEMD ($scope) ==="
    echo ""
    
    if [ "$scope" = "system" ]; then
        systemctl list-timers --no-pager
    else
        systemctl --user list-timers --no-pager 2>/dev/null || {
            echo "No hay timers de usuario activos"
            echo "Para habilitar timers de usuario persistentes:"
            echo "  sudo loginctl enable-linger $USER"
        }
    fi
}

# Mostrar estado de un timer específico
show_timer_status() {
    local timer_name="$1"
    local scope="${2:-user}"
    
    echo "=== ESTADO DEL TIMER: $timer_name ==="
    echo ""
    
    if [ "$scope" = "system" ]; then
        systemctl status "$timer_name.timer" --no-pager
        echo ""
        echo "=== HISTORIAL DE EJECUCIONES ==="
        journalctl -u "$timer_name.service" --no-pager -n 10
    else
        systemctl --user status "$timer_name.timer" --no-pager
        echo ""
        echo "=== HISTORIAL DE EJECUCIONES ==="
        journalctl --user -u "$timer_name.service" --no-pager -n 10
    fi
}

# Eliminar tarea programada
remove_scheduled_task() {
    local name="$1"
    local scope="${2:-user}"
    
    log "Eliminando tarea programada: $name"
    
    if [ "$scope" = "system" ]; then
        # Detener y deshabilitar timer
        systemctl stop "$name.timer" 2>/dev/null || true
        systemctl disable "$name.timer" 2>/dev/null || true
        
        # Eliminar archivos
        rm -f "$SYSTEMD_SYSTEM_DIR/$name.timer"
        rm -f "$SYSTEMD_SYSTEM_DIR/$name.service"
        
        systemctl daemon-reload
    else
        # Timer de usuario
        systemctl --user stop "$name.timer" 2>/dev/null || true
        systemctl --user disable "$name.timer" 2>/dev/null || true
        
        # Eliminar archivos
        rm -f "$SYSTEMD_USER_DIR/$name.timer"
        rm -f "$SYSTEMD_USER_DIR/$name.service"
        
        systemctl --user daemon-reload
    fi
    
    log "✓ Tarea '$name' eliminada"
}

# Crear ejemplos comunes
create_examples() {
    log "Creando ejemplos de tareas systemd"
    
    # Backup diario
    create_scheduled_task \
        "daily-backup" \
        "Backup diario del sistema" \
        "/usr/local/bin/backup_script.sh" \
        "0 2 * * *" \
        "/var/backups" \
        "root" \
        "true"
    
    # Limpieza de logs
    create_scheduled_task \
        "cleanup-logs" \
        "Limpieza de archivos de log antiguos" \
        "/usr/bin/find /var/log -name '*.log' -mtime +7 -delete" \
        "0 3 * * 0" \
        "/var/log" \
        "root" \
        "true"
    
    # Monitoreo de usuario
    create_scheduled_task \
        "user-monitor" \
        "Monitoreo de recursos del usuario" \
        "/home/$USER/bin/monitor.sh" \
        "*/5 * * * *" \
        "/home/$USER" \
        "$USER" \
        "false"
    
    log "✓ Ejemplos creados"
}

# Función de ayuda
show_help() {
    cat << EOF
Creador de Systemd Timers v1.0

USO:
    $0 COMANDO [OPCIONES]

COMANDOS:
    create NAME "DESCRIPTION" "COMMAND" "CRON_SCHEDULE" [WORKDIR] [USER] [SYSTEM]
        Crear nueva tarea programada con systemd
        
    list [system|user]
        Listar timers activos
        
    status TIMER_NAME [system|user]
        Mostrar estado de un timer específico
        
    remove TIMER_NAME [system|user]
        Eliminar tarea programada
        
    convert "CRON_EXPRESSION"
        Convertir expresión cron a formato systemd
        
    examples
        Crear ejemplos de tareas comunes
        
    help
        Mostrar esta ayuda

EJEMPLOS:
    # Crear backup diario del sistema
    $0 create daily-backup "Backup diario" "/usr/local/bin/backup.sh" "0 2 * * *" "/var/backups" "root" "true"
    
    # Crear monitoreo cada 5 minutos (usuario)
    $0 create user-monitor "Monitor de recursos" "\$HOME/bin/monitor.sh" "*/5 * * * *"
    
    # Listar timers del sistema
    $0 list system
    
    # Ver estado de un timer
    $0 status daily-backup system
    
    # Convertir expresión cron
    $0 convert "*/15 * * * *"

VENTAJAS DE SYSTEMD TIMERS SOBRE CRON:
    • Mejor logging con journald
    • Dependencias entre servicios
    • Manejo de errores más robusto
    • Capacidad de recuperación tras fallos
    • Integración con systemd
    • Timers persistentes

ARCHIVOS:
    Sistema: $SYSTEMD_SYSTEM_DIR
    Usuario: $SYSTEMD_USER_DIR
EOF
}

# Función principal
main() {
    case "${1:-help}" in
        create)
            if [ $# -lt 5 ]; then
                echo "Error: Argumentos insuficientes para 'create'"
                echo "Uso: $0 create NAME \"DESCRIPTION\" \"COMMAND\" \"CRON_SCHEDULE\" [WORKDIR] [USER] [SYSTEM]"
                exit 1
            fi
            create_scheduled_task "$2" "$3" "$4" "$5" "${6:-/tmp}" "${7:-$USER}" "${8:-false}"
            ;;
        list)
            list_timers "${2:-user}"
            ;;
        status)
            if [ $# -lt 2 ]; then
                echo "Error: Se requiere nombre del timer"
                exit 1
            fi
            show_timer_status "$2" "${3:-user}"
            ;;
        remove)
            if [ $# -lt 2 ]; then
                echo "Error: Se requiere nombre del timer"
                exit 1
            fi
            remove_scheduled_task "$2" "${3:-user}"
            ;;
        convert)
            if [ $# -lt 2 ]; then
                echo "Error: Se requiere expresión cron"
                exit 1
            fi
            local result
            result=$(cron_to_systemd "$2")
            echo "Cron: $2"
            echo "Systemd: $result"
            ;;
        examples)
            create_examples
            ;;
        help|--help|-h)
            show_help
            ;;
        *)
            echo "Comando no válido: $1"
            show_help
            exit 1
            ;;
    esac
}

# Ejecutar función principal
main "$@"

Mejores Prácticas

  • Usa rutas absolutas: Siempre especifica rutas completas en scripts cron
  • Variables de entorno: Define PATH y otras variables necesarias
  • Redirección de salida: Redirige stdout y stderr para evitar emails innecesarios
  • Logging propio: Implementa logging en tus scripts para debugging
  • Prueba frecuencias: Testa tareas con intervalos cortos antes de programar horarios definitivos
  • Considera systemd: Para sistemas modernos, evalúa usar systemd timers

Cuidados Importantes

  • Solapamiento de tareas: Evita que la misma tarea se ejecute múltiples veces simultáneamente
  • Recursos del sistema: Las tareas programadas pueden impactar el rendimiento
  • Backups regulares: Siempre respalda tu crontab antes de modificaciones grandes
  • Monitoreo: Verifica que las tareas se ejecuten correctamente
  • Permisos: Asegúrate de que los scripts tengan los permisos correctos

Ejercicios Prácticos

Ejercicio 1: Sistema de Backup Automatizado

Crea un sistema completo de backups programados:

  • Backup diario de bases de datos
  • Backup semanal de archivos del sistema
  • Backup mensual completo con rotación
  • Verificación automática de integridad de backups
  • Notificaciones por email del estado

Ejercicio 2: Sistema de Mantenimiento

Desarrolla tareas de mantenimiento automático:

  • Limpieza diaria de archivos temporales
  • Rotación semanal de logs
  • Actualización mensual del sistema
  • Verificación de certificados SSL
  • Reinicio programado de servicios críticos
[{"content": "Explore existing bash scripting course structure", "status": "completed", "activeForm": "Exploring existing bash scripting course structure"}, {"content": "Create module 7 for bash scripting course", "status": "completed", "activeForm": "Creating module 7 for bash scripting course"}, {"content": "Create module 8 for bash scripting course", "status": "completed", "activeForm": "Creating module 8 for bash scripting course"}, {"content": "Create module 11 for bash scripting course", "status": "in_progress", "activeForm": "Creating module 11 for bash scripting course"}]