Volver
Módulo 7 - Manejo Avanzado AVANZADO

getopts para Parámetros Robustos

Crea interfaces de línea de comandos profesionales con getopts de Bash

getopts es un comando built-in de Bash que proporciona una forma robusta y estándar de procesar opciones de línea de comandos. Es la herramienta preferida para crear scripts que se comportan como comandos profesionales de Unix/Linux.

¿Qué es getopts?

getopts permite procesar opciones que siguen las convenciones estándar de Unix:

  • Opciones de una letra precedidas por un guión: -h, -v, -f
  • Opciones que requieren argumentos: -f archivo.txt
  • Combinación de opciones: -hvf
  • Manejo automático de errores y mensajes

Sintaxis Básica

Elemento Descripción Ejemplo
getopts optstring variable Sintaxis básica del comando getopts "hvf:" opt
optstring Cadena que define opciones válidas "hvf:" (h,v,f con argumento)
: después de letra Indica que la opción requiere argumento f: requiere valor
$OPTARG Variable que contiene el argumento Valor de -f filename
$OPTIND Índice del siguiente argumento Para procesar argumentos restantes

Ejemplo Básico

getopts básico - simple.sh
bash
#!/bin/bash

# Script básico usando getopts
echo "Procesando opciones con getopts..."

# Variables por defecto
VERBOSE=false
HELP=false
FILENAME=""

# Procesar opciones
while getopts "hvf:" opt; do
    case $opt in
        h)
            HELP=true
            ;;
        v)
            VERBOSE=true
            ;;
        f)
            FILENAME="$OPTARG"
            ;;
        \?)
            echo "Opción inválida: -$OPTARG" >&2
            exit 1
            ;;
        :)
            echo "La opción -$OPTARG requiere un argumento." >&2
            exit 1
            ;;
    esac
done

# Mostrar ayuda si se solicita
if [ "$HELP" = true ]; then
    echo "Uso: $0 [-h] [-v] [-f archivo]"
    echo "  -h: Mostrar ayuda"
    echo "  -v: Modo verbose"
    echo "  -f: Especificar archivo"
    exit 0
fi

# Mostrar configuración si verbose está activo
if [ "$VERBOSE" = true ]; then
    echo "=== CONFIGURACIÓN ==="
    echo "Verbose: activado"
    echo "Archivo: ${FILENAME:-'no especificado'}"
    echo "===================="
fi

# Desplazar argumentos procesados
shift $((OPTIND-1))

# Mostrar argumentos restantes
if [ $# -gt 0 ]; then
    echo "Argumentos adicionales: $*"
fi

echo "Script ejecutado correctamente"
./simple.sh -h
./simple.sh -v -f config.txt arg1 arg2
./simple.sh -vf datos.csv extra

Ejemplo Avanzado: Herramienta de Backup

Herramienta profesional - backup_pro.sh
bash
#!/bin/bash

# Herramienta profesional de backup usando getopts
# Autor: Tu Nombre
# Versión: 1.0

# Variables globales
SCRIPT_NAME=$(basename "$0")
VERSION="1.0"
VERBOSE=false
DRY_RUN=false
COMPRESS=false
EXCLUDE_FILE=""
SOURCE_DIR=""
DEST_DIR=""
BACKUP_NAME=""

# Función para mostrar ayuda
mostrar_ayuda() {
    cat << EOF
$SCRIPT_NAME v$VERSION - Herramienta Profesional de Backup

USO:
    $SCRIPT_NAME [OPCIONES] ORIGEN DESTINO

OPCIONES:
    -h          Mostrar esta ayuda
    -v          Modo verbose (detallado)
    -n          Dry run (simular, no ejecutar)
    -c          Comprimir backup con gzip
    -e ARCHIVO  Archivo de exclusiones
    -b NOMBRE   Nombre personalizado del backup
    -V          Mostrar versión

ARGUMENTOS:
    ORIGEN      Directorio a respaldar
    DESTINO     Directorio donde guardar el backup

EJEMPLOS:
    $SCRIPT_NAME /home/usuario /backup
    $SCRIPT_NAME -v -c /documentos /backup
    $SCRIPT_NAME -n -e excludes.txt /proyecto /backup
    $SCRIPT_NAME -b "backup_importante" -v -c /datos /backup

ARCHIVO DE EXCLUSIONES:
    El archivo de exclusiones debe contener un patrón por línea:
    *.tmp
    *.log
    node_modules/
    .git/

CÓDIGOS DE SALIDA:
    0    Éxito
    1    Error en argumentos
    2    Error en validación
    3    Error durante el backup
EOF
}

# Función para mostrar versión
mostrar_version() {
    echo "$SCRIPT_NAME versión $VERSION"
}

# Función para logging
log() {
    local nivel="$1"
    shift
    local mensaje="$*"
    local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    case "$nivel" in
        "INFO")
            [ "$VERBOSE" = true ] && echo "[$timestamp] INFO: $mensaje"
            ;;
        "WARN")
            echo "[$timestamp] WARN: $mensaje" >&2
            ;;
        "ERROR")
            echo "[$timestamp] ERROR: $mensaje" >&2
            ;;
        "DEBUG")
            [ "$VERBOSE" = true ] && echo "[$timestamp] DEBUG: $mensaje"
            ;;
    esac
}

# Función para validar directorio
validar_directorio() {
    local dir="$1"
    local tipo="$2"
    
    if [ ! -d "$dir" ]; then
        log "ERROR" "El directorio $tipo '$dir' no existe"
        return 1
    fi
    
    case "$tipo" in
        "origen")
            if [ ! -r "$dir" ]; then
                log "ERROR" "No se puede leer el directorio origen '$dir'"
                return 1
            fi
            ;;
        "destino")
            if [ ! -w "$dir" ]; then
                log "ERROR" "No se puede escribir en el directorio destino '$dir'"
                return 1
            fi
            ;;
    esac
    
    return 0
}

# Función para validar archivo de exclusiones
validar_exclusiones() {
    local archivo="$1"
    
    if [ ! -f "$archivo" ]; then
        log "ERROR" "El archivo de exclusiones '$archivo' no existe"
        return 1
    fi
    
    if [ ! -r "$archivo" ]; then
        log "ERROR" "No se puede leer el archivo de exclusiones '$archivo'"
        return 1
    fi
    
    return 0
}

# Función para realizar el backup
realizar_backup() {
    local origen="$1"
    local destino="$2"
    local nombre="$3"
    
    # Crear nombre del backup si no se especifica
    if [ -z "$nombre" ]; then
        nombre="backup_$(date +%Y%m%d_%H%M%S)"
    fi
    
    log "INFO" "Iniciando backup de '$origen' a '$destino/$nombre'"
    
    # Preparar comando rsync
    local rsync_cmd="rsync -av"
    local rsync_options=""
    
    # Agregar archivo de exclusiones si existe
    if [ -n "$EXCLUDE_FILE" ]; then
        rsync_options="$rsync_options --exclude-from=$EXCLUDE_FILE"
    fi
    
    # Construir comando completo
    local comando="$rsync_cmd $rsync_options \"$origen/\" \"$destino/$nombre/\""
    
    if [ "$DRY_RUN" = true ]; then
        log "INFO" "DRY RUN - Se ejecutaría: $comando"
        return 0
    fi
    
    # Ejecutar backup
    log "DEBUG" "Ejecutando: $comando"
    eval "$comando"
    local resultado=$?
    
    if [ $resultado -eq 0 ]; then
        log "INFO" "Backup completado exitosamente"
        
        # Comprimir si se solicita
        if [ "$COMPRESS" = true ]; then
            log "INFO" "Comprimiendo backup..."
            if [ "$DRY_RUN" = true ]; then
                log "INFO" "DRY RUN - Se comprimiría: $destino/$nombre.tar.gz"
            else
                tar -czf "$destino/$nombre.tar.gz" -C "$destino" "$nombre"
                if [ $? -eq 0 ]; then
                    rm -rf "$destino/$nombre"
                    log "INFO" "Backup comprimido como: $destino/$nombre.tar.gz"
                else
                    log "WARN" "Error al comprimir, manteniendo directorio sin comprimir"
                fi
            fi
        fi
    else
        log "ERROR" "Error durante el backup (código: $resultado)"
        return 3
    fi
    
    return 0
}

# Procesar opciones con getopts
while getopts "hvncVe:b:" opt; do
    case $opt in
        h)
            mostrar_ayuda
            exit 0
            ;;
        v)
            VERBOSE=true
            log "DEBUG" "Modo verbose activado"
            ;;
        n)
            DRY_RUN=true
            log "INFO" "Modo DRY RUN activado - no se realizarán cambios"
            ;;
        c)
            COMPRESS=true
            log "DEBUG" "Compresión activada"
            ;;
        V)
            mostrar_version
            exit 0
            ;;
        e)
            EXCLUDE_FILE="$OPTARG"
            log "DEBUG" "Archivo de exclusiones: $EXCLUDE_FILE"
            ;;
        b)
            BACKUP_NAME="$OPTARG"
            log "DEBUG" "Nombre de backup personalizado: $BACKUP_NAME"
            ;;
        \?)
            log "ERROR" "Opción inválida: -$OPTARG"
            echo "Use '$SCRIPT_NAME -h' para ver la ayuda"
            exit 1
            ;;
        :)
            log "ERROR" "La opción -$OPTARG requiere un argumento"
            echo "Use '$SCRIPT_NAME -h' para ver la ayuda"
            exit 1
            ;;
    esac
done

# Desplazar opciones procesadas
shift $((OPTIND-1))

# Validar número de argumentos
if [ $# -ne 2 ]; then
    log "ERROR" "Se requieren exactamente 2 argumentos: ORIGEN y DESTINO"
    echo "Use '$SCRIPT_NAME -h' para ver la ayuda"
    exit 1
fi

SOURCE_DIR="$1"
DEST_DIR="$2"

# Validaciones
log "INFO" "Validando parámetros..."

if ! validar_directorio "$SOURCE_DIR" "origen"; then
    exit 2
fi

if ! validar_directorio "$DEST_DIR" "destino"; then
    exit 2
fi

if [ -n "$EXCLUDE_FILE" ]; then
    if ! validar_exclusiones "$EXCLUDE_FILE"; then
        exit 2
    fi
fi

# Mostrar configuración si verbose está activo
if [ "$VERBOSE" = true ]; then
    echo ""
    echo "========================================="
    echo "          CONFIGURACIÓN DE BACKUP       "
    echo "========================================="
    echo "Origen:         $SOURCE_DIR"
    echo "Destino:        $DEST_DIR"
    echo "Nombre:         ${BACKUP_NAME:-'automático'}"
    echo "Verbose:        $VERBOSE"
    echo "Dry Run:        $DRY_RUN"
    echo "Comprimir:      $COMPRESS"
    echo "Exclusiones:    ${EXCLUDE_FILE:-'ninguna'}"
    echo "========================================="
    echo ""
fi

# Realizar backup
realizar_backup "$SOURCE_DIR" "$DEST_DIR" "$BACKUP_NAME"
exit $?

Manejo Avanzado de Errores

Manejo de errores - errores_getopts.sh
bash
#!/bin/bash

# Manejo avanzado de errores con getopts

# Configurar manejo silencioso de errores
OPTERR=0

# Variables
ARCHIVO=""
NUMERO=""
DIRECTORIO=""
QUIET=false

# Función de error personalizada
error_opcion() {
    local opcion="$1"
    local mensaje="$2"
    
    if [ "$QUIET" = false ]; then
        echo "Error: $mensaje" >&2
        echo "Use '$0 -h' para ver la ayuda" >&2
    fi
    exit 1
}

# Función de ayuda
mostrar_ayuda() {
    cat << EOF
Uso: $0 [OPCIONES]

OPCIONES:
    -f ARCHIVO    Especificar archivo (requerido)
    -n NUMERO     Especificar número (1-100)
    -d DIR        Especificar directorio
    -q            Modo silencioso
    -h            Mostrar ayuda

Ejemplo: $0 -f datos.txt -n 50 -d /tmp
EOF
}

# Validar número en rango
validar_numero() {
    local num="$1"
    
    # Verificar que sea un número
    if ! [[ "$num" =~ ^[0-9]+$ ]]; then
        return 1
    fi
    
    # Verificar rango
    if [ "$num" -lt 1 ] || [ "$num" -gt 100 ]; then
        return 1
    fi
    
    return 0
}

# Procesar opciones
while getopts ":f:n:d:qh" opt; do
    case $opt in
        f)
            ARCHIVO="$OPTARG"
            # Validar que el archivo no empiece con -
            if [[ "$ARCHIVO" =~ ^- ]]; then
                error_opcion "f" "El nombre de archivo no puede empezar con '-': $ARCHIVO"
            fi
            ;;
        n)
            NUMERO="$OPTARG"
            if ! validar_numero "$NUMERO"; then
                error_opcion "n" "El número debe estar entre 1 y 100: $NUMERO"
            fi
            ;;
        d)
            DIRECTORIO="$OPTARG"
            if [[ "$DIRECTORIO" =~ ^- ]]; then
                error_opcion "d" "El directorio no puede empezar con '-': $DIRECTORIO"
            fi
            ;;
        q)
            QUIET=true
            ;;
        h)
            mostrar_ayuda
            exit 0
            ;;
        :)
            case $OPTARG in
                f) error_opcion "f" "La opción -f requiere un nombre de archivo" ;;
                n) error_opcion "n" "La opción -n requiere un número" ;;
                d) error_opcion "d" "La opción -d requiere un directorio" ;;
                *) error_opcion "$OPTARG" "La opción -$OPTARG requiere un argumento" ;;
            esac
            ;;
        \?)
            # Manejar casos especiales
            case $OPTARG in
                '') error_opcion "" "Se encontró una opción vacía" ;;
                *) error_opcion "$OPTARG" "Opción desconocida: -$OPTARG" ;;
            esac
            ;;
    esac
done

# Validar opciones requeridas
if [ -z "$ARCHIVO" ]; then
    error_opcion "f" "La opción -f (archivo) es requerida"
fi

# Validaciones adicionales
if [ -n "$ARCHIVO" ] && [ ! -f "$ARCHIVO" ]; then
    error_opcion "f" "El archivo especificado no existe: $ARCHIVO"
fi

if [ -n "$DIRECTORIO" ] && [ ! -d "$DIRECTORIO" ]; then
    error_opcion "d" "El directorio especificado no existe: $DIRECTORIO"
fi

# Mostrar configuración final
if [ "$QUIET" = false ]; then
    echo "Configuración válida:"
    echo "  Archivo: $ARCHIVO"
    echo "  Número: ${NUMERO:-'no especificado'}"
    echo "  Directorio: ${DIRECTORIO:-'no especificado'}"
    echo "  Silencioso: $QUIET"
fi

echo "Script ejecutado correctamente"

Combinando getopts con Argumentos Posicionales

Combinación avanzada - procesador_completo.sh
bash
#!/bin/bash

# Script que combina getopts con argumentos posicionales
# Ejemplo: ./script.sh -v -f config.txt proceso1 proceso2 proceso3

# Variables
VERBOSE=false
CONFIG_FILE=""
FORMATO="txt"
PROCESOS=()

# Procesar opciones
while getopts "vf:F:" opt; do
    case $opt in
        v)
            VERBOSE=true
            ;;
        f)
            CONFIG_FILE="$OPTARG"
            ;;
        F)
            FORMATO="$OPTARG"
            ;;
        \?)
            echo "Opción inválida: -$OPTARG" >&2
            exit 1
            ;;
        :)
            echo "La opción -$OPTARG requiere un argumento" >&2
            exit 1
            ;;
    esac
done

# Desplazar opciones procesadas
shift $((OPTIND-1))

# Los argumentos restantes son los procesos
PROCESOS=("$@")

# Validar que se proporcionaron procesos
if [ ${#PROCESOS[@]} -eq 0 ]; then
    echo "Error: Debe especificar al menos un proceso"
    echo "Uso: $0 [-v] [-f config] [-F formato] proceso1 [proceso2 ...]"
    exit 1
fi

# Mostrar configuración si verbose está activo
if [ "$VERBOSE" = true ]; then
    echo "=== CONFIGURACIÓN ==="
    echo "Config file: ${CONFIG_FILE:-'ninguno'}"
    echo "Formato: $FORMATO"
    echo "Procesos: ${#PROCESOS[@]}"
    for i in "${!PROCESOS[@]}"; do
        echo "  $((i+1)). ${PROCESOS[i]}"
    done
    echo "===================="
fi

# Procesar cada proceso
for proceso in "${PROCESOS[@]}"; do
    echo "Procesando: $proceso"
    # Aquí iría la lógica de procesamiento
    [ "$VERBOSE" = true ] && echo "  ✓ $proceso procesado"
done

echo "Todos los procesos han sido procesados"

Tips y Mejores Prácticas

  • Usa OPTERR=0: Para manejar errores personalizados
  • Siempre incluye \":\": Al inicio de optstring para manejo de errores silencioso
  • Valida argumentos requeridos: Después del bucle getopts
  • Documenta tus opciones: Con funciones de ayuda detalladas
  • Usa shift $((OPTIND-1)): Para acceder a argumentos posicionales
  • Maneja casos especiales: Opciones que parecen argumentos

Limitaciones de getopts

  • Solo opciones cortas: No soporta --long-options nativamente
  • Una letra por opción: No puede usar -ab como dos opciones separadas automáticamente
  • No agrupa automáticamente: -abc debe ser manejado manualmente
  • Para opciones largas: Considera usar getopt (externo) o manejo manual

Comparación con Otros Métodos

Método Ventajas Desventajas Uso Recomendado
getopts Built-in, rápido, estándar Solo opciones cortas Scripts Unix/Linux estándar
getopt (externo) Opciones largas y cortas Dependencia externa Scripts complejos con muchas opciones
Manual (case) Control total, flexible Más código, propenso a errores Casos muy específicos
$1, $2, $3... Simple para pocos argumentos No escalable, sin validación Scripts muy simples

Ejercicios Prácticos

Ejercicio 1: Herramienta de Log

Crea un script que use getopts para:

  • Analizar archivos de log (-f archivo)
  • Filtrar por nivel (-l INFO|WARN|ERROR)
  • Especificar formato de salida (-F json|csv|txt)
  • Incluir modo verbose (-v) y ayuda (-h)

Ejercicio 2: Generador de Reportes

Desarrolla un script que:

  • Procese múltiples directorios como argumentos posicionales
  • Use getopts para opciones como -r (recursivo), -s (tamaño), -t (tipo)
  • Genere reportes en diferentes formatos
  • Incluya validación robusta de todos los parámetros