Cómo recibe un script de Bash lo que escribes detrás de su nombre: los parámetros posicionales $1, $# y "$@", por qué las comillas importan tanto, cómo consumirlos con shift, cómo analizar opciones cortas con getopts y largas con un bucle, y qué código de salida devolver en cada caso.
También te puede interesar
$1, $#, $@ y $0
Lo que escribes detrás del nombre del script llega numerado: $1 es el primer argumento, $2 el segundo, y así sucesivamente; $# dice cuántos hay, "$@" los da todos y $0 es el nombre con el que se llamó al script. Este se ejecutó como ./basico.sh uno 'dos tres' cuatro:
#!/usr/bin/env bash
echo "Script: $0"
echo "Número de argumentos: $#"
echo "Primero: $1"
echo "Segundo: $2"
i=1
for argumento in "$@"; do
echo " $i: $argumento"
i=$((i + 1))
donesalidaScript: ./basico.sh Número de argumentos: 3 Primero: uno Segundo: dos tres 1: uno 2: dos tres 3: cuatro
Las comillas al llamar al script deciden qué es un argumento: 'dos tres' llega como uno solo, con su espacio. A partir del décimo hay que usar llaves: ${10}, porque $10 es $1 seguido de un cero.
«$@» frente a $* y «$*»
#!/usr/bin/env bash echo 'Con "$@":' printf ' [%s]\n' "$@" echo 'Con $*:' printf ' [%s]\n' $* echo 'Con "$*":' printf ' [%s]\n' "$*"
salidaCon "$@": [uno] [dos tres] [cuatro] Con $*: [uno] [dos] [tres] [cuatro] Con "$*": [uno dos tres cuatro]
Solo "$@", con comillas, conserva los argumentos tal como llegaron. $* sin comillas los vuelve a partir por los espacios, y "$*" los junta todos en una sola cadena, útil para mensajes pero no para pasarlos a otra orden. ShellCheck avisa del segundo caso:
shellcheckIn comillas.sh line 5:
printf ' [%s]\n' $*
^-- SC2048 (warning): Use "$@" (with quotes) to prevent whitespace problems.
^-- SC2086 (info): Double quote to prevent globbing and word splitting.
Did you mean:
printf ' [%s]\n' "$*"
For more information:
https://www.shellcheck.net/wiki/SC2048 -- Use "$@" (with quotes) to prevent...
https://www.shellcheck.net/wiki/SC2086 -- Double quote to prevent globbing ...Recorrer y consumir con shift
#!/usr/bin/env bash
while (( $# > 0 )); do
echo "Quedan $#, el primero es «$1»"
shift
done
echo "Ya no queda ninguno"salidaQuedan 3, el primero es «-v» Quedan 2, el primero es «informe.txt» Quedan 1, el primero es «otro archivo.txt» Ya no queda ninguno
shift descarta $1 y corre los demás una posición: el antiguo $2 pasa a ser $1. Es la base de cualquier analizador de opciones hecho a mano, y shift 2 descarta dos de golpe.
Opciones cortas con getopts
Para opciones del estilo -v -n 3 -o salida.txt, Bash trae getopts, que entiende opciones agrupadas (-vn3), opciones con valor y el -- que marca el final de las opciones. Este script muestra las primeras líneas de varios archivos:
#!/usr/bin/env bash
uso() {
cat <<FIN
Uso: ${0##*/} [-v] [-n NÚMERO] [-o ARCHIVO] ARCHIVO...
Muestra las primeras líneas de uno o varios archivos.
-v mostrar más detalles
-n NÚMERO cuántas líneas mostrar (por omisión, 5)
-o ARCHIVO añadir el resultado a ARCHIVO en vez de mostrarlo
-h mostrar esta ayuda
FIN
}
detalle=0
lineas=5
salida=""
while getopts ":vn:o:h" opcion; do
case $opcion in
v) detalle=1 ;;
n) lineas=$OPTARG ;;
o) salida=$OPTARG ;;
h) uso; exit 0 ;;
:) echo "Error: la opción -$OPTARG necesita un valor" >&2; uso >&2; exit 2 ;;
\?) echo "Error: opción desconocida -$OPTARG" >&2; uso >&2; exit 2 ;;
esac
done
shift $((OPTIND - 1))
if [[ ! $lineas =~ ^[0-9]+$ ]]; then
echo "Error: -n espera un número, no «$lineas»" >&2
exit 2
fi
if (( $# == 0 )); then
echo "Error: falta al menos un archivo" >&2
uso >&2
exit 2
fi
if (( detalle )); then
echo "Leyendo $lineas líneas de $# archivo(s)" >&2
fi
for archivo in "$@"; do
if [[ ! -r $archivo ]]; then
echo "Error: no puedo leer «$archivo»" >&2
exit 1
fi
if [[ -n $salida ]]; then
head -n "$lineas" -- "$archivo" >> "$salida"
else
head -n "$lineas" -- "$archivo"
fi
done
if (( detalle )); then
echo "Hecho" >&2
fiLas claves:
- En
":vn:o:h", cada letra es una opción y los dos puntos detrás de una letra indican que lleva valor, que llega en$OPTARG. - Los dos puntos del principio activan el modo silencioso:
getoptsno imprime sus propios mensajes y te deja decidir qué decir. Una opción desconocida llega como?y una opción a la que le falta el valor, como:; en los dos casos,$OPTARGcontiene la letra. - Al terminar,
shift $((OPTIND - 1))quita las opciones ya leídas, y en"$@"quedan solo los archivos. - El valor de
-nse valida con una expresión regular antes de usarlo.${0##*/}quita la ruta del nombre del script para el mensaje de uso.
Así responde a distintas llamadas, con el código de salida de cada una:
terminal$ bash opciones.sh -h Uso: opciones.sh [-v] [-n NÚMERO] [-o ARCHIVO] ARCHIVO... Muestra las primeras líneas de uno o varios archivos. -v mostrar más detalles -n NÚMERO cuántas líneas mostrar (por omisión, 5) -o ARCHIVO añadir el resultado a ARCHIVO en vez de mostrarlo -h mostrar esta ayuda $ bash opciones.sh -n 2 frutas.txt manzana pera $ bash opciones.sh -vn3 -o resumen.txt frutas.txt; echo "código: $?" Leyendo 3 líneas de 1 archivo(s) Hecho código: 0 $ cat resumen.txt manzana pera uva $ bash opciones.sh -n; echo "código: $?" Error: la opción -n necesita un valor Uso: opciones.sh [-v] [-n NÚMERO] [-o ARCHIVO] ARCHIVO... Muestra las primeras líneas de uno o varios archivos. -v mostrar más detalles -n NÚMERO cuántas líneas mostrar (por omisión, 5) -o ARCHIVO añadir el resultado a ARCHIVO en vez de mostrarlo -h mostrar esta ayuda código: 2 $ bash opciones.sh -x frutas.txt; echo "código: $?" Error: opción desconocida -x Uso: opciones.sh [-v] [-n NÚMERO] [-o ARCHIVO] ARCHIVO... Muestra las primeras líneas de uno o varios archivos. -v mostrar más detalles -n NÚMERO cuántas líneas mostrar (por omisión, 5) -o ARCHIVO añadir el resultado a ARCHIVO en vez de mostrarlo -h mostrar esta ayuda código: 2 $ bash opciones.sh -n tres frutas.txt; echo "código: $?" Error: -n espera un número, no «tres» código: 2 $ bash opciones.sh -- -raro.txt; echo "código: $?" Soy un archivo cuyo nombre empieza por guion código: 0
El último caso muestra para qué sirve --: lo que viene detrás ya no son opciones, aunque empiece por un guion. Sin él, getopts tomaría -raro.txt por un grupo de opciones (-r, -a…) y se quejaría de la primera. Y como el script pasa también -- a head, el archivo se lee sin que head lo confunda con una de sus opciones.
Opciones largas: un bucle con case
getopts no entiende opciones largas como --salida=informe.txt. Existe la orden getopt (sin la s) de util-linux, que sí las entiende, pero solo en Linux: la de macOS es otra. Lo más portable es un bucle con case y shift:
#!/usr/bin/env bash
detalle=0
numero=5
salida=""
while (( $# > 0 )); do
case $1 in
-h|--ayuda) echo "Uso: ${0##*/} [--detalle] [--numero N] [--salida ARCHIVO] ARCHIVO..."; exit 0 ;;
-v|--detalle) detalle=1; shift ;;
-n|--numero)
if (( $# < 2 )); then echo "Error: $1 necesita un valor" >&2; exit 2; fi
numero=$2; shift 2 ;;
--numero=*) numero=${1#*=}; shift ;;
-o|--salida)
if (( $# < 2 )); then echo "Error: $1 necesita un valor" >&2; exit 2; fi
salida=$2; shift 2 ;;
--salida=*) salida=${1#*=}; shift ;;
--) shift; break ;;
-*) echo "Error: opción desconocida $1" >&2; exit 2 ;;
*) break ;;
esac
done
echo "detalle=$detalle numero=$numero salida=$salida"
printf 'Archivo: %s\n' "$@"salidadetalle=1 numero=3 salida=informe.txt Archivo: -raro.txt Archivo: notas.txt
${1#*=} se queda con lo que va detrás del primer =, así que se aceptan las dos formas, --numero 3 y --numero=3.
Los códigos de salida
Un script que acepta argumentos debería terminar con un código que otros scripts puedan comprobar con $? o con if. Estas son las convenciones más extendidas:
- CódigoSignificaQuién lo usa
- 0todo fue bientodas las órdenes
- 1error generalla mayoría de los programas
- 2uso incorrecto: opciones o argumentos mal puestosBash con sus órdenes internas,
grep,lsy buena parte de las de GNU - 126la orden existe pero no se puede ejecutarBash, p. ej. un script sin permiso de ejecución
- 127orden no encontradaBash
- 128 + Nterminado por la señal N (130 = Ctrl+C, 137 = kill -9)Bash
Todos los scripts de esta página se ejecutaron con Bash 5.3 en Ubuntu 26.04, en un contenedor limpio sin acceso a la red, y pasaron ShellCheck 0.11 sin avisos antes de publicarla; las salidas están copiadas de esa ejecución.