Cómo usar getopts para parsear opciones cortas y largas en scripts de Bash
Introducción
Cuando escribís scripts de Bash, es común que necesitéis recibir opciones desde la línea de comandos: por ejemplo, especificar un archivo de entrada con -f o definir un archivo de salida con -o. El built‑in getopts de Bash es la forma más portátil y segura de parsear esas opciones cortas (de un solo guión). Aunque getopts no soporta directamente opciones largas (--file, --output), podés combinarlo con una lógica adicional para soportarlas sin perder sus ventajas.
Este tutorial está dirigido a desarrolladores, administradores de sistemas y cualquiera que escriba scripts de shell y quiera mejorar la experiencia de usuario de sus herramientas. Al finalizar, vas a saber:
- La sintaxis básica de
getopts. - Cómo definir opciones que requieren argumento obligatorio o que son opcionales.
- Cómo manejar tanto opciones cortas como largas en el mismo script.
- Qué buenas prácticas seguir y qué errores evitar.
¿Qué es getopts?
getopts es un comando interno de Bash que procesa de forma secuencial las opciones que aparecen en $@ (los parámetros pasados al script). Su sintaxis es:
getopts optstring varname [args]optstring: una cadena que lista las letras de las opciones esperadas. Si una opción necesita un argumento, se le agrega dos puntos (:). Si el argumento es opcional, se le agregan dos puntos dobles (::).varname: la variable dondegetoptsguarda la letra de la opción encontrada en cada iteración.[args]: opcionalmente, podés pasar un arreglo distinto de$@para quegetoptslo procese (útil para testing).
El bucle típico es:
while getopts "optstring" opt; do
case $opt in
# manejo de cada opción
esac
doneDespués del bucle, shift $((OPTIND-1)) elimina las opciones ya procesadas, dejando solo los argumentos restantes (operands).
Sintaxis y manejo de argumentos
Opciones sin argumento
Si una opción no necesita argumento, simplemente la listás en optstring sin puntos:
getopts "hf" optAquí -h y -f son flags booleanos.
Opciones con argumento obligatorio
Agregá un dos puntos después de la letra:
getopts "f:o:" optAhora -f y -o deben ir seguidos de un valor (p.ej. -f archivo.txt o -o salida.txt). Si el usuario omite el argumento, getopts genera un error y coloca ? en opt.
Opciones con argumento opcional
Usá dos puntos dobles:
getopts "f::o::" optEn este caso, si la opción está presente pero sin valor inmediato, la variable OPTARG queda vacía. Tené en cuenta que, para que Bash reconozca el argumento opcional, tiene que estar pegado a la opción (p.ej. -fvalor o `-f valor separado por espacio no funciona para opcional; Bash trata el espacio como separador y considera que falta argumento).
Ejemplo 1: Script que acepta -f y -o (ambos obligatorios)
Guarda este código como procesar.sh:
#!/usr/bin/env bash
# Mensaje de uso
usage() {
cat <<EOF
Uso: $0 -f <archivo_entrada> -o <archivo_salida>
Opciones:
-f Archivo de entrada (obligatorio)
-o Archivo de salida (obligatorio)
-h Mostrar esta ayuda
EOF
}
# Variables por defecto
INPUT_FILE=""
OUTPUT_FILE=""
# Parseo de opciones cortas
while getopts "f:o:h" opt; do
case $opt in
f) INPUT_FILE="$OPTARG" ;;
o) OUTPUT_FILE="$OPTARG" ;;
h) usage; exit 0 ;;
\?) echo "Opción inválida: -$OPTARG" >&2; usage; exit 1 ;;
:) echo "La opción -$OPTARG requiere un argumento" >&2; usage; exit 1 ;;
esac
done
# Eliminar opciones ya procesadas
shift $((OPTIND-1))
# Validación
if [[ -z "$INPUT_FILE" || -z "$OUTPUT_FILE" ]]; then
echo "Error: faltan argumentos obligatorios" >&2
usage
exit 1
fi
# Acción principal (ejemplo simple)
if [[ ! -f "$INPUT_FILE" ]]; then
echo "Error: el archivo de entrada '$INPUT_FILE' no existe" >&2
exit 1
fi
cp "$INPUT_FILE" "$OUTPUT_FILE"
echo "Archivo copiado de '$INPUT_FILE' a '$OUTPUT_FILE'"Salida esperada
$ ./procesar.sh -f datos.txt -o copia.txt
Archivo copiado de 'datos.txt' a 'copia.txt'Si olvidás un argumento:
$ ./procesar.sh -f datos.txt
La opción -o requiere un argumento
Uso: ./procesar.sh -f <archivo_entrada> -o <archivo_salida>
Opciones:
-f Archivo de entrada (obligatorio)
-o Archivo de salida (obligatorio)
-h Mostrar esta ayudaSi pasás una opción desconocida:
$ ./procesar.sh -x
Opción inválida: -x
Uso: ./procesar.sh -f <archivo_entrada> -o <archivo_salida>
...Manejo de opciones largas (--file, --output)
getopts solo reconoce opciones de un solo guión. Para soportar opciones largas, hay dos enfoques comunes:
- Pre‑procesar
$@convirtiendo--opción=valoren-o valor(o-ovalor) antes de llamar agetopts. - Usar un
casemanual después del bucle degetoptspara atender las opciones largas que quedaron sin procesar.
A continuación se muestra una combinación que permite tanto formas cortas como largas, con argumentos obligatorios y opcionales.
Estrategia de pre‑procesado
#!/usr/bin/env bash
# Función que transforma largas en cortas
normalize_args() {
local normalized=()
while [[ $# -gt 0 ]]; do
case $1 in
--file)
# necesita argumento obligatorio
if [[ -z "$2" ]]; then
echo "Error: --file requiere un argumento" >&2
exit 2
fi
normalized+=(-f "$2" )
shift 2
;;
--file=*)
normalized+=(-f "${1#*=}" )
shift
;;
--output)
if [[ -z "$2" ]]; then
echo "Error: --output requiere un argumento" >&2
exit 2
fi
normalized+=(-o "$2" )
shift 2
;;
--output=*)
normalized+=(-o "${1#*=}" )
shift
;;
--help)
normalized+=(-h)
shift
;;
*) # cualquier otro argumento (incluyendo opciones cortas ya en formato -x o -xvalor)
normalized+=("$_1
shift
;;
esac
done
# devolver el arreglo normalizado
printf '%s
' "${normalized[@]}"
}
# Reemplazar los parámetros originales con los normalizados
eval set -- "$(normalize_args "$@
)"" # Nota: el eval es necesario para preservar espacios y comillas
# Ahora usamos getopts solo con opciones cortas
INPUT_FILE=""
OUTPUT_FILE=""
while getopts "f:o:h" opt; do
case $opt in
f) INPUT_FILE="$OPTARG" ;;
o) OUTPUT_FILE="$OPTARG" ;;
h) usage; exit 0 ;;
\?) echo "Opción inválida: -$OPTARG" >&2; usage; exit 1 ;;
:) echo "La opción -$OPTARG requiere un argumento" >&2; usage; exit 1 ;;
esac
done
shift $((OPTIND-1))
# resto del script (igual que antes)Nota: El
eval set -- ...es una técnica común para volver a cargar los parámetros posicionales después de la normalización. Si preferís evitareval, podés usar un arreglo y luegoset -- "${array[@]}". En Bash 4.4+ podés hacer:
normalized_args=($(normalize_args "$@
))" # no funciona bien con espacios
set -- "${normalized_args[@]}" # forma seguraAlternativa: case manual después de getopts
Si no querés tocar los parámetros, podés dejar que getopts procese solo las opciones cortas y luego iterar sobre lo que queda ($@ después del shift) para detectar --file o --output.
while getopts "f:o:h" opt; do
case $opt in
f) INPUT_FILE="$OPTARG" ;;
o) OUTPUT_FILE="$OPTARG" ;;
h) usage; exit 0 ;;
\?) echo "Opción inválida: -$OPTARG" >&2; usage; exit 1 ;;
:) echo "La opción -$OPTARG requiere un argumento" >&2; usage; exit 1 ;;
esac
done
shift $((OPTIND-1))
# Procesar opciones largas restantes
while [[ $# -gt 0 ]]; do
case $1 in
--file)
if [[ -z "$2" ]]; then
echo "Error: --file requiere un argumento" >&2; usage; exit 2
fi
INPUT_FILE="$2"
shift 2
;;
--file=*)
INPUT_FILE="${1#*=}"
shift
;;
--output)
if [[ -z "$2" ]]; then
echo "Error: --output requiere un argumento" >&2; usage; exit 2
fi
OUTPUT_FILE="$2"
shift 2
;;
--output=*)
OUTPUT_FILE="${1#*=}"
shift
;;
--help)
usage; exit 0
;;
*) # argumento posicional (ej. archivos de entrada sin bandera)
break
;;
esac
doneEste segundo enfoque es más explícito y evita el uso de eval, pero tenés que recordar que cualquier opción corta que haya quedado sin ser reconocida por getopts (porque estaba mal escrita) quedará en $@ y será tratada como argumento posicional, lo cual puede generar confusiones. Por eso, la técnica de normalización suele ser más robusta.
Ejemplo completo: script que acepta -f/--file, -o/--output y -v/--verbose (opcional)
#!/usr/bin/env bash
usage() {
cat <<EOF
Uso: $0 [OPCIONES]
Opciones:
-f, --file <archivo> Archivo de entrada (obligatorio)
-o, --output <archivo> Archivo de salida (obligatorio)
-v, --verbose Modo verbose (opcional)
-h, --help Mostrar esta ayuda
EOF
}
INPUT_FILE=""
OUTPUT_FILE=""
VERBOSE=0
# ---------- Normalización de opciones largas a cortas ----------
normalize() {
local out=()
while [[ $# -gt 0 ]]; do
case $1 in
--file)
[[ -z "$2" ]] && { echo "--file necesita argumento" >&2; exit 2; }
out+=(-f "$2\
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.