Dominando los Parámetros Posicionales: Una Guía para los Argumentos de Scripts Bash

Desbloquea el poder de los scripts Bash dinámicos dominando los parámetros posicionales. Esta guía completa explica cómo acceder a los argumentos de la línea de comandos usando `$1`, `$2` y variables especiales como `$#` (número de argumentos) y el crucial `"$@"` (todos los argumentos). Aprende las mejores prácticas esenciales para la validación de entrada, comprende la diferencia entre `\$*` y `\$@`, y ve ejemplos prácticos para escribir scripts robustos y con verificación de errores que se adaptan perfectamente a la entrada del usuario.

Dominando los Parámetros Posicionales: Una Guía para los Argumentos de Scripts Bash

Los scripts Bash se vuelven mucho más útiles cuando aceptan argumentos en lugar de obligarte a editar variables dentro del archivo. Un script de respaldo debería aceptar un directorio de origen. Un script de despliegue debería aceptar un nombre de entorno. Un script de limpieza debería aceptar una o más rutas. Esos valores llegan como parámetros posicionales: $1, $2, $3, y así sucesivamente.

La parte complicada no es leer $1. La parte complicada es manejar argumentos faltantes, argumentos con espacios, banderas opcionales y el momento en que tu script crece de "solo para mí" a algo que otra persona ejecutará a las 2 a.m.


La Anatomía de los Parámetros Posicionales

Los parámetros posicionales son variables especiales definidas por el shell que corresponden a las palabras proporcionadas en la línea de comandos después del nombre del script. Están numerados secuencialmente, comenzando desde 1.

Parámetro Descripción Valor de Ejemplo (al ejecutar ./script.sh archivo1 dir/)
$0 El nombre del script en sí (o función). ./script.sh
$1 El primer argumento pasado al script. archivo1
$2 El segundo argumento pasado al script. dir/
$N El enésimo argumento (donde N > 0).
${10} Los argumentos más allá de 9 deben estar encerrados entre llaves.

Accediendo a Argumentos Más Allá de $9

Mientras que los argumentos 1 al 9 se acceden directamente como $1 a $9, acceder al décimo argumento y siguientes requiere encerrar el número entre llaves para evitar ambigüedad con variables de entorno u operaciones de cadenas (por ejemplo, ${10} en lugar de $10).


Parámetros Especiales Esenciales para Scripting

Más allá de los parámetros numéricos, Bash proporciona varias variables especiales críticas que se relacionan con el conjunto de argumentos en su totalidad. Estos son indispensables para la validación y la iteración.

Contando Argumentos con $#

La variable especial $# contiene el número total de argumentos de línea de comandos pasados al script (excluyendo $0). Esta es quizás la variable más importante para implementar la validación de entrada.

#!/bin/bash

if [ "$#" -eq 0 ]; then
    echo "Error: No se proporcionaron argumentos."
    echo "Uso: $0 <archivo_entrada>"
    exit 1
fi

echo "Proporcionaste $# argumentos."

Todos los Argumentos: $@ y $*

Las variables $@ y $* ambas representan la lista completa de argumentos, pero se comportan de manera diferente, especialmente cuando se citan.

$* (Cadena Única)

Cuando se cita doblemente ("$*"), toda la lista de parámetros posicionales se trata como un solo argumento, separado por el primer carácter de la variable IFS (Separador de Campo Interno) (generalmente un espacio).

  • Si los argumentos de entrada son: arg1 arg2 arg3
  • "$*" se expande a: "arg1 arg2 arg3" (un solo elemento)

$@ (Cadenas Separadas - Preferido)

Cuando se cita doblemente ("$@"), cada parámetro posicional se trata como un argumento separado y citado. Este es el método estándar y preferido para iterar sobre argumentos, ya que preserva correctamente los argumentos que contienen espacios.

  • Si los argumentos de entrada son: arg1 "arg con espacio" arg3
  • "$@" se expande a: "arg1" "arg con espacio" "arg3" (tres elementos distintos)

Por Qué las Citas Importan: Una Demostración

Considere un script ejecutado con argumentos: ./test.sh 'hola mundo' archivo.txt

#!/bin/bash

# $* sin citar divide en espacios y generalmente está mal.
echo "-- Iterando usando $* sin citar --"
for item in $*; do
    echo "Elemento: $item"
done

# "$@" citado preserva cada argumento original.
echo "-- Iterando usando "$@" citado --"
for item in "$@"; do
    echo "Elemento: $item"
done

Con ./test.sh 'hola mundo' archivo.txt, el bucle sin citar imprime hola y mundo como elementos separados. El bucle "$@" mantiene hola mundo como un solo argumento. Esa diferencia es la razón por la que los usuarios experimentados de shell recurren a "$@" casi automáticamente.


Técnicas Prácticas para el Manejo de Argumentos

1. Script Básico de Recuperación de Argumentos

Este script simple demuestra cómo acceder a parámetros específicos y usar $0 para proporcionar comentarios útiles.

deploy_service.sh:

#!/bin/bash
# Uso: deploy_service.sh <nombre_servicio> <entorno>

NOMBRE_SERVICIO="$1"
ENTORNO="$2"

# Verificación de validación (mínimo dos argumentos)
if [ "$#" -lt 2 ]; then
    echo "Uso: $0 <nombre_servicio> <entorno>"
    exit 1
fi

echo "Iniciando despliegue para el servicio: $NOMBRE_SERVICIO"
echo "Entorno objetivo: $ENTORNO"

# Ejecutar comando usando los parámetros validados
ssh admin@servidor-"$ENTORNO" "/ruta/a/iniciar $NOMBRE_SERVICIO"

2. Validación Robusta de Entrada

Los buenos scripts siempre validan la entrada antes de proceder. Esto incluye verificar el conteo ($#) y a menudo verificar el contenido de los argumentos (por ejemplo, verificar si un argumento es un número o una ruta de archivo válida).

#!/bin/bash

# 1. Verificar el Número de Argumentos (Debe ser exactamente 3)
if [ "$#" -ne 3 ]; then
    echo "Error: Este script requiere tres argumentos (origen, destino, usuario)."
    echo "Uso: $0 <ruta_origen> <ruta_destino> <usuario>"
    exit 1
fi

RUTA_ORIGEN="$1"
RUTA_DESTINO="$2"
USUARIO="$3"

# 2. Verificar Contenido (Ejemplo: Verificar que la ruta de origen exista)
if [ ! -f "$RUTA_ORIGEN" ]; then
    echo "Error: El archivo de origen '$RUTA_ORIGEN' no se encuentra o no es un archivo."
    exit 2
fi

# Si la validación pasa, proceder
echo "Copiando $RUTA_ORIGEN a $RUTA_DESTINO como usuario $USUARIO..."

Consejo de Mejores Prácticas: Siempre proporciona una declaración Uso: clara y concisa cuando la validación falle. Esto ayuda a los usuarios a corregir rápidamente su invocación de comando.

3. Iterando Argumentos con shift

El comando shift es una excelente herramienta para procesar argumentos secuencialmente, a menudo usado cuando se manejan banderas simples o cuando se procesan argumentos uno por uno dentro de un bucle while.

shift descarta el argumento actual $1, mueve $2 a $1, $3 a $2, y decrementa $# en uno. Esto te permite procesar el primer argumento y luego iterar hasta que no queden argumentos.

#!/bin/bash

# Procesar una bandera -v simple y luego listar los archivos restantes

VERBOSO=false

if [ "$1" = "-v" ]; then
    VERBOSO=true
    shift  # Descartar la bandera -v y desplazar los argumentos hacia arriba
fi

if $VERBOSO; then
    echo "Modo verboso activado."
fi

if [ "$#" -eq 0 ]; then
    echo "No se especificaron archivos."
    exit 0
fi

echo "Procesando $# archivos restantes:"
for archivo in "$@"; do
    if $VERBOSO; then
        echo "Verificando archivo: $archivo"
    fi
    # ... lógica de procesamiento aquí
done

Nota: shift es útil para análisis simples. Para scripts complejos con muchas banderas, getopts suele ser una mejor opción para opciones cortas. El manejo de opciones largas varía según la plataforma, así que prueba cuidadosamente si usas getopt externo.

Un Analizador Más Realista

Muchos scripts internos comienzan con una bandera opcional y un valor requerido. Aquí hay un pequeño patrón que se mantiene legible:

#!/usr/bin/env bash
set -u

ejecucion_en_seco=false
entorno=""

uso() {
    echo "Uso: $0 [--dry-run] --env <dev|staging|prod> <archivo>..." >&2
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        --dry-run)
            ejecucion_en_seco=true
            shift
            ;;
        --env)
            if [ "$#" -lt 2 ]; then
                echo "Error: --env requiere un valor." >&2
                uso
                exit 2
            fi
            entorno="$2"
            shift 2
            ;;
        --help|-h)
            uso
            exit 0
            ;;
        --)
            shift
            break
            ;;
        -*)
            echo "Error: opción desconocida: $1" >&2
            uso
            exit 2
            ;;
        *)
            break
            ;;
    esac
done

if [ -z "$entorno" ]; then
    echo "Error: --env es requerido." >&2
    uso
    exit 2
fi

if [ "$#" -eq 0 ]; then
    echo "Error: proporciona al menos un archivo." >&2
    uso
    exit 2
fi

for archivo in "$@"; do
    if [ ! -f "$archivo" ]; then
        echo "Error: archivo no encontrado: $archivo" >&2
        exit 3
    fi

    if $ejecucion_en_seco; then
        echo "Se subiría $archivo a $entorno"
    else
        echo "Subiendo $archivo a $entorno"
        # comando de subida va aquí
    fi
done

Nota los detalles aburridos. Los mensajes de error van a stderr. -- significa "dejar de analizar opciones", lo que permite que alguien pase un nombre de archivo que comience con un guión. El bucle final de archivos usa "$@", por lo que notas de lanzamiento.txt sigue siendo un nombre de archivo.

Errores Comunes

El error más común es olvidar las comillas:

cp $1 $2

Eso se rompe cuando cualquiera de las rutas contiene espacios o caracteres glob del shell. Usa:

cp -- "$1" "$2"

El -- le dice a muchos comandos que el análisis de opciones ha terminado, lo que ayuda si una ruta comienza con -.

Otro error común es validar demasiado tarde. Si tu script espera dos argumentos, verifica eso antes de hacer cualquier cosa destructiva:

if [ "$#" -ne 2 ]; then
    echo "Uso: $0 <origen> <destino>" >&2
    exit 2
fi

Usa códigos de salida distintos cuando ayude a la persona que llama. Un error de uso podría ser 2; un archivo faltante podría ser 3; un comando externo fallido puede mantener su propio estado. No necesitas una taxonomía gigante de códigos de salida, pero devolver 0 después de una invocación incorrecta hace que la automatización sea menos confiable.

Las Funciones También Tienen Parámetros Posicionales

Dentro de una función Bash, $1 y $2 se refieren a los argumentos de la función, no a los argumentos originales del script.

log_copia() {
    local origen="$1"
    local destino="$2"

    echo "Copiando $origen a $destino"
    cp -- "$origen" "$destino"
}

log_copia "$1" "$2"

Eso es útil, pero puede sorprenderte si esperabas que $1 dentro de la función significara el primer argumento a nivel de script. Pasa valores explícitamente. Hace que la función sea más fácil de probar y más fácil de reutilizar.

Reenviando Argumentos a Otro Comando

Muchos scripts envoltorio existen solo para agregar un poco de configuración antes de llamar a otro comando. En ese caso, "$@" es lo que mantiene honesto al envoltorio.

#!/usr/bin/env bash
set -e

export APP_ENV=staging
exec /usr/local/bin/miapp "$@"

Si alguien ejecuta:

./ejecutar-staging.sh --config "config con espacios.yml" --verbose

el comando envuelto recibe los mismos tres argumentos. Si usaste $* o $@ sin comillas, la ruta de configuración podría dividirse en varias palabras.

exec es opcional, pero a menudo es útil en envoltorios porque reemplaza el proceso del shell con el proceso objetivo. Eso hace que las señales se comporten de manera más predecible bajo systemd, Docker o un supervisor de procesos.

Valores Predeterminados Sin Sorpresas

A veces un argumento debería ser opcional. La expansión de parámetros de Bash puede ayudar:

entorno="${1:-dev}"

Eso significa "usa $1 si está establecido y no vacío; de lo contrario, usa dev". Esto está bien para scripts locales amigables, pero ten cuidado con scripts de producción. Un valor predeterminado silencioso puede desplegar en el entorno equivocado si alguien olvida un argumento.

Para comandos riesgosos, prefiere entrada explícita:

if [ "$#" -lt 1 ]; then
    echo "Uso: $0 <entorno>" >&2
    exit 2
fi

Los valores predeterminados son mejores cuando la consecuencia es pequeña, como predeterminar un nivel de registro o un directorio de salida. Son riesgosos cuando el argumento elige un servidor, elimina datos o cambia un objetivo de despliegue.

Parámetros Posicionales y set -u

Muchos scripts Bash usan set -u para que las variables no establecidas causen un error. Eso puede detectar errores tipográficos, pero también cambia cómo se comportan los parámetros posicionales faltantes.

#!/usr/bin/env bash
set -u

echo "Primer argumento: $1"

Ejecuta ese script sin argumentos y Bash sale con un error de "variable no vinculada". Ese error es técnicamente correcto, pero no es amigable. Valida $# antes de leer parámetros requeridos:

if [ "$#" -lt 1 ]; then
    echo "Uso: $0 <archivo-entrada>" >&2
    exit 2
fi

archivo_entrada="$1"

Para parámetros opcionales bajo set -u, usa una expansión protegida:

modo="${2:-default}"

Eso mantiene el modo estricto útil sin hacer que los valores opcionales faltantes bloqueen el script.

Cuándo los Parámetros Posicionales Son la Interfaz Incorrecta

Los parámetros posicionales son excelentes para comandos pequeños:

respaldo.sh /var/www /backup/www.tar.gz

Se vuelven difíciles de leer cuando el script toma muchos valores:

desplegar.sh prod us-east-1 api v2.4.1 true false 30

Nadie quiere recordar qué significa el quinto argumento. Una vez que un script llega a ese punto, usa banderas con nombre o un archivo de configuración:

desplegar.sh --env prod --region us-east-1 --service api --version v2.4.1 --timeout 30

El código es ligeramente más largo, pero la línea de comandos se vuelve autodocumentada. Eso es un buen intercambio para scripts utilizados por un equipo.

El buen manejo de parámetros posicionales es principalmente disciplina: valida temprano, cita cada expansión a menos que quieras división intencionalmente, usa "$@" para reenviar argumentos y mantén los mensajes de uso cerca de las comprobaciones que los activan. Esos hábitos hacen que los scripts pequeños sobrevivan a nombres de archivos reales, usuarios reales y automatización real.