1. continue: saltar a la vuelta siguiente
continue abandona la vuelta actual y pasa directamente a la siguiente. Lo que queda del cuerpo no se ejecuta para ese elemento. En un for, se toma el elemento siguiente; en un while o un until, se vuelve a evaluar la condición.
$ for i in 1 2 3 4 5; do (( i == 3 )) && continue; echo -n "$i "; done; echo
1 2 4 5
Su uso más habitual es el de una cláusula de guarda dentro del bucle, la misma idea del tema 12 aplicada a cada vuelta: al comienzo del cuerpo se descartan los elementos que no deben procesarse, y el resto del cuerpo puede asumir que el elemento es válido.
for archivo in "$dir"/*; do
[[ -f "$archivo" ]] || continue # solo archivos comunes
[[ -s "$archivo" ]] || continue # que no estén vacíos
[[ "$archivo" == *.tmp ]] && continue # ni temporales
gzip -- "$archivo"
done
Es preferible a envolver todo el cuerpo en un if con varias condiciones, por la misma razón que en el tema 12: cada descarte queda en su propia línea, con su motivo, y el trabajo real queda sin sangría extra.
2. break: terminar el bucle
break termina el bucle por completo, en el punto en que se ejecuta. El script sigue con lo que viene después de done.
$ for i in 1 2 3 4 5; do (( i == 4 )) && break; echo -n "$i "; done; echo "fin"
1 2 3 fin
Tiene dos usos principales. El primero, detener una búsqueda en cuanto se encuentra lo que se buscaba, que veremos en la sección 6. El segundo, salir de un bucle while true cuya condición de salida está en medio del cuerpo, como en el tema 15:
while true; do
read -rp "Puerto (1-65535): " puerto
if [[ "$puerto" =~ ^[0-9]+$ ]] && (( puerto >= 1 && puerto <= 65535 )); then
break
fi
echo "Puerto no válido: $puerto" >&2
done
echo "Usaremos el puerto $puerto"
Después de break, la variable del bucle y cualquier otra conservan el valor que tenían en ese momento. Por eso, en el ejemplo, puerto contiene la respuesta válida al salir del bucle.
3. Bucles anidados: break 2 y continue 2
Cuando hay un bucle dentro de otro, break y continue afectan solo al más interno. Para actuar sobre un bucle exterior se les pasa un número: break 2 termina el bucle interno y el que lo contiene; continue 2 abandona el interno y pasa a la vuelta siguiente del exterior.
$ for a in 1 2; do for b in x y z; do [[ $b == y ]] && continue 2; echo -n "$a$b "; done; done; echo
1x 2x
$ for a in 1 2; do for b in x y; do [[ $a == 2 ]] && break 2; echo -n "$a$b "; done; done; echo
1x 1y
El caso típico es una búsqueda en dos niveles, como buscar un archivo en varios servidores: en cuanto aparece en uno, no hace falta seguir ni con ese servidor ni con los demás.
encontrado=""
for dir in "$HOME/.config/app" /etc/app /usr/local/etc/app; do
for nombre in app.conf config.ini; do
if [[ -r "$dir/$nombre" ]]; then
encontrado="$dir/$nombre"
break 2 # el primero que aparece gana
fi
done
done
[[ -n "$encontrado" ]] || { echo "No se encontró la configuración" >&2; exit 1; }
echo "Usando $encontrado"
El orden de la lista de directorios define la prioridad: la configuración del usuario antes que la del sistema. Más de dos niveles de anidamiento, o un break 3, suelen indicar que convendría sacar el bucle interior a una función, que puede terminar con return (tema 18).
4. break, return o exit
Tres comandos interrumpen la ejecución, cada uno a un nivel distinto:
| Comando | Termina | La ejecución sigue |
|---|---|---|
continue | La vuelta actual del bucle. | En la vuelta siguiente. |
break | El bucle. | Después de done. |
return | La función (tema 19). | Después de la llamada a la función. |
exit | El script entero. | En ningún lado: el proceso termina. |
Elegir el nivel adecuado es parte del diseño. Ante un elemento problemático en una lista, continue sigue con los demás; ante un error que hace inútil seguir, como un destino sin espacio, exit termina todo. Usar break o continue fuera de un bucle no hace nada útil: Bash avisa con un mensaje y sigue.
5. continue y los contadores
En un for, continue es siempre seguro: el elemento siguiente lo pone el propio for. En un while que avanza con un contador, en cambio, hay una trampa: si el incremento está al final del cuerpo, continue lo saltea, y el bucle repite la misma vuelta para siempre.
i=0
while (( i < 10 )); do
(( i % 2 == 0 )) && continue # con i = 0, salta… el incremento
echo "$i"
i=$(( i + 1 ))
done
La corrección es incrementar al comienzo del cuerpo, antes de cualquier continue, o usar un for (( )), cuyo paso se ejecuta siempre, aunque se salte la vuelta. Es una buena razón para preferir for (( )) cuando se cuenta.
6. Buscar y detenerse
Un patrón muy frecuente combina un bucle, una condición y break: recorrer una lista hasta encontrar el primer elemento que cumple algo. Por ejemplo, el primer puerto libre a partir de uno dado, o el primer servidor que responde:
servidores=(espejo1.example.com espejo2.example.com espejo3.example.com)
elegido=""
for s in "${servidores[@]}"; do
if curl -fsS --max-time 3 -o /dev/null "https://$s/estado"; then
elegido="$s"
break
fi
echo "$s no responde; pruebo el siguiente" >&2
done
[[ -n "$elegido" ]] || { echo "Ningún servidor responde" >&2; exit 1; }
echo "Descargando desde $elegido"
La variable elegido empieza vacía y solo toma valor si se encontró algo. Después del bucle, comprobarla distingue los dos finales posibles: salió por break, con un servidor elegido, o recorrió toda la lista sin encontrar ninguno. Es más claro que depender del valor que quedó en la variable del bucle, que en el segundo caso sería el último elemento, aunque no haya funcionado.
7. Menús con select
Bash tiene un bucle especial para construir menús: select. Muestra una lista numerada de opciones, pregunta un número, y asigna a una variable la opción correspondiente. Su forma es la de un for:
$ select color in rojo verde azul; do echo "Elegiste $color"; break; done
1) rojo
2) verde
3) azul
#? 2
Elegiste verde
A diferencia de for, select no recorre la lista: es un bucle infinito que, en cada vuelta, pregunta y ejecuta el cuerpo con la opción elegida. Por eso casi siempre lleva un break: sin él, el menú vuelve a preguntar después de cada elección. Termina solo cuando se ejecuta break o cuando la entrada se acaba, por ejemplo al pulsar Ctrl+D.
La lista de opciones sigue las mismas reglas que la de for: puede ser un array ("${opciones[@]}"), un patrón de archivos (*.conf) o una lista escrita, y cada elemento conserva sus espacios.
8. PS3, REPLY y las opciones inválidas
Tres detalles completan el funcionamiento de select:
| Elemento | Qué es |
|---|---|
PS3 | El texto de la pregunta. Por defecto es #? , poco claro para cualquiera; conviene asignarlo siempre antes del select. |
REPLY | Lo que el usuario escribió, tal cual. Sirve para reconocer respuestas que no son números de la lista, como 0 o q para salir. |
La variable del select | La opción elegida, o vacía si lo escrito no corresponde a ninguna opción. |
Si el usuario escribe un número fuera de rango o un texto, select no da error: ejecuta el cuerpo con la variable vacía. El cuerpo debe comprobarlo. Y si pulsa Enter sin escribir nada, select vuelve a mostrar el menú.
PS3="Elige un entorno (0 para salir): "
select entorno in desarrollo pruebas produccion; do
if [[ "$REPLY" == 0 ]]; then
echo "Cancelado."
exit 0
elif [[ -n "$entorno" ]]; then
break # opción válida
fi
echo "Opción no válida: $REPLY" >&2
done
echo "Desplegando en $entorno"
1) desarrollo
2) pruebas
3) produccion
Elige un entorno (0 para salir): 7
Opción no válida: 7
Elige un entorno (0 para salir): 2
Desplegando en pruebas
El menú se escribe en la salida de errores, no en la estándar, de modo que no se mezcla con los datos si la salida del script se redirige.
9. select con case
Cuando cada opción del menú lleva a una acción distinta, select se combina con el case del tema 13, y el menú se repite hasta que se elige salir. Es la estructura de un pequeño programa interactivo:
PS3="¿Qué quieres hacer? "
opciones=("Ver espacio en disco" "Ver memoria" "Ver procesos que más consumen" "Salir")
select opcion in "${opciones[@]}"; do
case "$REPLY" in
1) df -h / ;;
2) free -h ;;
3) ps aux --sort=-%cpu | head -6 ;;
4) break ;;
*) echo "Elige un número del 1 al ${#opciones[@]}" >&2 ;;
esac
done
Aquí el case compara REPLY, el número escrito, en lugar del texto de la opción: si mañana se cambia la redacción de una opción, el case no se rompe. Sin break en las opciones de tareas, el menú vuelve a aparecer después de cada una. Para volver a mostrar la lista, que puede haber quedado lejos en la pantalla, basta con pulsar Enter.
10. Los límites de select
select es práctico para scripts de uso personal o de administración, pero tiene límites que conviene conocer:
- Necesita a alguien delante. Lo dicho en el tema 9 vale aquí con más razón: un script que depende de un menú no puede ejecutarse desde
cron. La elección debería poder hacerse también con un argumento, y el menú quedar solo para cuando falta y hay una terminal ([[ -t 0 ]]). - El formato es fijo. La numeración, el orden en columnas cuando hay muchas opciones y el aspecto no se pueden cambiar. Para menús más elaborados, se escribe un bucle propio con
printfyread, o se recurre a herramientas comowhiptailodialog, que dibujan ventanas en la terminal. - Con listas largas se vuelve incómodo. Cien archivos numerados no caben en una pantalla. Para eso conviene un filtro previo, o una herramienta de búsqueda interactiva como
fzf.
11. Un script completo
Restaurar un respaldo es una tarea poco frecuente, que se hace a mano y en momentos de apuro: el lugar ideal para un menú que evite tener que escribir nombres de archivo largos. Este script lista los respaldos de un directorio, deja elegir uno, pide confirmación y lo extrae en un directorio de destino.
#!/usr/bin/env bash
#
# restaurar — elige un respaldo de una lista y lo extrae en un directorio.
#
# Uso: restaurar [DIRECTORIO_DE_RESPALDOS] [DESTINO]
# Por defecto, /respaldos y ./restaurado. Necesita una terminal.
#
set -euo pipefail
shopt -s nullglob
origen="${1:-/respaldos}"
destino="${2:-./restaurado}"
respaldos=("$origen"/*.tar.gz)
if (( ${#respaldos[@]} == 0 )); then
echo "No hay respaldos en $origen" >&2
exit 1
fi
[[ -t 0 ]] || { echo "${0##*/} necesita una terminal para elegir" >&2; exit 2; }
PS3="Número del respaldo (0 para salir): "
select respaldo in "${respaldos[@]##*/}"; do # solo los nombres, sin el directorio
if [[ "$REPLY" == 0 ]]; then
echo "Cancelado."
exit 0
elif [[ -n "$respaldo" ]]; then
break
fi
echo "Opción no válida: $REPLY" >&2
done
[[ -n "${respaldo:-}" ]] || exit 0 # la entrada terminó (Ctrl+D)
read -rp "¿Extraer $respaldo en $destino? [s/N] " r
if [[ "${r,,}" != s* ]]; then
echo "Cancelado."
exit 0
fi
mkdir -p -- "$destino"
tar -xzf "$origen/$respaldo" -C "$destino"
echo "Restaurado $respaldo en $destino"
$ restaurar /respaldos /tmp/recuperado
1) web-2026-10-12.tar.gz
2) web-2026-10-13.tar.gz
3) web-2026-10-14.tar.gz
Número del respaldo (0 para salir): 7
Opción no válida: 7
Número del respaldo (0 para salir): 2
¿Extraer web-2026-10-13.tar.gz en /tmp/recuperado? [s/N] s
Restaurado web-2026-10-13.tar.gz en /tmp/recuperado
"${respaldos[@]##*/}"aplica la expansión del tema 6 a cada elemento del array, como vimos en el tema 8: el menú muestra solo los nombres, más legibles, y la ruta completa se reconstruye al extraer.- Las comprobaciones previas descartan los dos casos en que el menú no tiene sentido: no hay respaldos, o no hay nadie para elegir. Cada una con su propio código de salida.
- El
selecttiene tres salidas:0para cancelar, una opción válida conbreak, y el fin de la entrada. En el último casorespaldoqueda vacía o sin asignar, y la línea que sigue al bucle lo detecta, con${respaldo:-}para no chocar conset -u. - La confirmación usa
[s/N]: extraer puede sobrescribir archivos en el destino, así que la respuesta por defecto es la segura, como establecimos en el tema 9.
12. Errores frecuentes
| Escribiste | Qué pasa | Corrección |
|---|---|---|
continue antes del incremento en un while | El bucle repite la misma vuelta para siempre. | Incrementar al comienzo, o usar for (( )). |
break en un bucle interno esperando salir de todos | Solo termina el interno; el externo sigue. | break 2, o una función con return. |
| Usar la variable del bucle para saber si se encontró algo | Si no hubo break, contiene el último elemento. | Una variable aparte, vacía hasta que se encuentra. |
select sin break | El menú vuelve a preguntar después de cada elección. | break tras una opción válida. |
No comprobar si la variable del select está vacía | Una respuesta inválida se procesa como si fuera una opción. | [[ -n "$opcion" ]] antes de usarla. |
select sin asignar PS3 | Aparece #?, que nadie entiende. | PS3="Elige…: " |
| Un menú como única forma de elegir | El script no puede usarse desde cron ni desde otro script. | Aceptar la elección como argumento; menú solo con terminal. |
13. Resumen
continueabandona la vuelta actual y pasa a la siguiente; sirve como cláusula de guarda al comienzo del cuerpo.breaktermina el bucle; las variables conservan el valor que tenían.break Nycontinue Nactúan sobre bucles exteriores en bucles anidados.continuetermina una vuelta,breakun bucle,returnuna función yexitel script.- En un
whilecon contador, uncontinueque saltea el incremento produce un bucle infinito. - Para buscar, se recorre con
breakal encontrar, y una variable inicialmente vacía indica si se encontró algo. selectmuestra un menú numerado, asigna la opción elegida a una variable y repite hasta unbreako el fin de la entrada.PS3es el texto de la pregunta;REPLY, lo que se escribió; una respuesta inválida deja vacía la variable.selectconcase "$REPLY"construye menús de tareas que se repiten hasta elegir salir.- Los menús necesitan una terminal: el script debería aceptar la elección también como argumento.
14. Preguntas de repaso
Intenta responder cada una antes de abrir la respuesta.
1. ¿Qué diferencia hay entre continue y break?
continue abandona solo la vuelta actual y sigue con la siguiente. break termina el bucle completo.
2. ¿Qué muestra for i in 1 2 3 4; do (( i == 2 )) && continue; (( i == 4 )) && break; echo $i; done?
1 y 3. El 2 se salta con continue, y al llegar al 4 el bucle termina antes de mostrarlo.
3. ¿Qué hace break 2?
Termina el bucle en que se ejecuta y también el bucle que lo contiene. La ejecución sigue después del done del bucle exterior.
4. ¿Por qué este bucle no termina: i=0; while (( i < 5 )); do (( i == 0 )) && continue; i=$(( i + 1 )); done?
Porque con i en 0, continue salta el incremento, la condición se evalúa de nuevo con i todavía en 0, y así indefinidamente.
5. Después de un bucle de búsqueda, ¿por qué no conviene usar la variable del bucle para saber qué se encontró?
Porque si el bucle recorrió toda la lista sin encontrar nada, la variable contiene el último elemento, y no hay forma de distinguir ese caso de un hallazgo. Se usa una variable aparte, vacía hasta que se encuentra algo.
6. ¿Qué contienen la variable del select y REPLY si el usuario escribe 9 en un menú de tres opciones?
La variable queda vacía, porque 9 no corresponde a ninguna opción, y REPLY contiene 9.
7. ¿Para qué sirve PS3?
Es el texto que select muestra al preguntar. Por defecto es #? .
8. ¿Cuándo termina un select?
Cuando se ejecuta break (o exit) dentro de él, o cuando se acaba la entrada, por ejemplo al pulsar Ctrl+D.
9. ¿Por qué en un menú de tareas conviene que el case compare $REPLY y no el texto de la opción?
Porque el número no cambia si se modifica la redacción de la opción. Comparar el texto obligaría a cambiar también el case.
10. ¿Por qué un script que usa select debería aceptar la opción también como argumento?
Porque un menú necesita a alguien delante. Sin una forma no interactiva de elegir, el script no puede ejecutarse desde cron ni desde otro script.
Para practicar
- Escribe un bucle que recorra los números del 1 al 30 y muestre solo los que no son múltiplos de 3, usando
continue. - Escribe un bucle que busque el primer puerto libre a partir del 8000, comprobando con
/dev/tcpsi hay algo escuchando, y se detenga conbreak. - Reproduce el bucle infinito de la sección 5 (detenlo con Ctrl+C) y corrígelo de dos formas.
- Escribe un
selectque muestre los archivos.confde/etcy abra el elegido conless. - Construye un menú de tareas con
selectycasecon al menos cuatro acciones útiles para ti, y una para salir. - Crea algunos respaldos de prueba con
tar -czfen un directorio y usarestaurarpara extraer uno. Modifícalo para que acepte el nombre del respaldo como tercer argumento, y use el menú solo si falta.