Bucles en Bash: for, while y until con ejemplos prácticos
Introducción
Si trabajás con la línea de comandos o escribís scripts de automatización, seguramente te encontraste con la necesidad de repetir una acción varias veces: procesar un lote de archivos, leer líneas de un archivo de configuración, o simplemente contar hasta un número determinado. En Bash, los bucles son la herramienta fundamental para lograr esto de forma clara y eficiente.
Este tutorial está dirigido a desarrolladores, administradores de sistemas y cualquier persona que quiera profundizar en el uso de bucles en Bash. Al terminar, vas a saber:
- La sintaxis del bucle
fory sus variantes (rangos y arrays). - Cómo usar
whileyuntilpara bucles condicionales. - Qué salida esperar de cada ejemplo, para que puedas validar tus scripts rápidamente.
- Buenas prácticas y errores comunes que evitás al escribir bucles.
Preparate para ver ejemplos concretos, con la salida esperada incluida, y llevar tus scripts al siguiente nivel.
El bucle for: sintaxis básica
El bucle for en Bash recorre una lista de elementos y ejecuta un bloque de código por cada uno. Su forma más simple es:
for variable in lista; do
# comandos
done
Donde lista puede ser una secuencia explícita de palabras, el resultado de un comando, o una expansión de rango.
Ejemplo 1: recorrer una lista estática
Supongamos que queremos imprimir los nombres de algunos colores:
#!/bin/bash
for color in rojo verde azul; do
echo "El color es $color"
done
Salida esperada:
El color es rojo
El color es verde
El color es azul
Ejemplo 2: usar la expansión de rangos
Bash permite generar secuencias numéricas con la sintaxis {inicio..fin} y opcionalmente un paso con {inicio..fin..paso}.
Contar del 1 al 5
for i in {1..5}; do
echo "Número: $i"
done
Salida esperada:
Número: 1
Número: 2
Número: 3
Número: 4
Número: 5
Contar de 0 a 20 de 5 en 5
for i in {0..20..5}; do
echo "Múltiplo de 5: $i"
done
Salida esperada:
Múltiplo de 5: 0
Múltiplo de 5: 5
Múltiplo de 5: 10
Múltiplo de 5: 15
Múltiplo de 5: 20
Ejemplo 3: usar seq (alternativa a rangos)
Si necesitás más flexibilidad (por ejemplo, números con decimales), podés usar el comando seq dentro de la expansión de comandos:
for i in $(seq 0 0.5 2); do
echo "Valor: $i"
done
Salida esperada:
Valor: 0
Valor: 0.5
Valor: 1.0
Valor: 1.5
Valor: 2.0
El bucle for con arrays
Los arrays permiten almacenar múltiples valores en una variable y luego iterar sobre ellos de forma ordenada. En Bash, los arrays se definen con paréntesis y se accede a sus elementos con ${array[indice]} o, para recorrer todos, con ${array[@]}.
Definir y recorrer un array
#!/bin/bash
frutas=(manzana banana naranja)
for fruta in "${frutas[@]}" ; do
echo "Fruta: $fruta"
done
Salida esperada:
Fruta: manzana
Fruta: banana
Fruta: naranja
Acceder al índice y al valor
Si necesitás tanto el índice como el valor, podés usar un bucle for sobre los índices:
for i in "${!frutas[@]}" ; do
echo "Índice $i → ${frutas[$i]}"
done
Salida esperada:
Índice 0 → manzana
Índice 1 → banana
Índice 2 → naranja
Array leído desde un comando
Es común llenar un array con la salida de un comando, por ejemplo, listar archivos:
archivos=(*.txt)
for archivo in "${archivos[@]}" ; do
echo "Procesando $archivo"
done
Si en el directorio existen nota.txt, registro.txt y resumen.txt, la salida será:
Procesando nota.txt
Procesando registro.txt
Procesando resumen.txt
El bucle while: repetición mientras se cumple una condición
El bucle while evalúa una condición antes de cada iteración. Mientras la condición sea verdadera (código de salida 0), se ejecuta el bloque.
Sintaxis:
while [ condición ]; do
# comandos
done
Ejemplo 1: contar hasta 10
contador=1
while [ $contador -le 10 ]; do
echo "Contador: $contador"
((contador++))
done
Salida esperada:
Contador: 1
Contador: 2
Contador: 3
Contador: 4
Contador: 5
Contador: 6
Contador: 7
Contador: 8
Contador: 9
Contador: 10
Ejemplo 2: leer líneas de un archivo
Supongamos que tenés un archivo usuarios.txt con una lista de nombres, uno por línea. Querés imprimir cada línea precedida por un número.
#!/bin/bash
archivo="usuarios.txt"
linea_num=1
while IFS= read -r linea; do
echo "$linea_num: $linea"
((linea_num++))
done < "$archivo"
Si usuarios.txt contiene:
Ana
Luis
Carla
La salida esperada es:
1: Ana
2: Luis
3: Carla
Ejemplo 3: esperar a que un proceso termine
A veces necesitás esperar a que un proceso (identificado por su PID) finalice antes de continuar. Podés usar while con kill -0 que verifica si el proceso existe.
#!/bin/bash
pid=$1 # se pasa como argumento
while kill -0 $pid 2>/dev/null; do
echo "Esperando a que el proceso $pid termine..."
sleep 2
done
echo "El proceso $pid ha finalizado."
Si el proceso con PID 1234 tarda 5 segundos en terminar, la salida será algo como:
Esperando a que el proceso 1234 termine...
Esperando a que el proceso 1234 termine...
El proceso 1234 ha finalizado.
El bucle until: repetición hasta que se cumpla una condición
El bucle until es el opuesto lógico de while: se ejecuta mientras la condición sea falsa y se detiene cuando se vuelve verdadera.
Sintaxis:
until [ condición ]; do
# comandos
done
Ejemplo 1: esperar a que un archivo aparezca
Imaginemos que otro proceso va a crear un archivo llamado listo.flag. Queremos ejecutar una acción solo cuando ese archivo exista.
#!/bin/bash
flag="listo.flag"
until [ -f "$flag" ]; do
echo "Esperando la creación de $flag..."
sleep 1
done
echo "¡Archivo detectado! Continuando...
Supongamos que el archivo se crea después de 3 segundos. La salida será:
Esperando la creación de listo.flag...
Esperando la creación de listo.flag...
Esperando la creación of listo.flag...
¡Archivo detectado! Continuando...
Ejemplo 2: generar una contraseña aleatoria hasta que cumpla ciertos criterios
Queremos una contraseña de al menos 8 caracteres que contenga al menos un número y una letra mayúscula. Podemos generar candidatos aleatorios y repetir hasta que cumpla.
#!/bin/bash
generar() {
# genera 10 caracteres alfanuméricos
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c10
}
password=""
until [[ $password =~ [A-Z] ]] && [[ $password =~ [0-9] ]] && [[ ${#password} -ge 8 ]]; do
password=$(generar)
done
echo "Contraseña generada: $password"
Una posible salida (variará cada ejecución):
Contraseña generada: G7hK9pQ2sR
Bucles anidados
En situaciones reales, es común necesitar bucles dentro de otros bucles. Por ejemplo, procesar todos los archivos de varios directorios.
Ejemplo: renombrar archivos en subdirectorios
Supongamos una estructura:
docs/
├── proyecto1/
│ ├── informe.txt
│ └── notas.txt
└── proyecto2/
├── resumen.txt
└── borrador.txt
Queremos añadir el prefijo bak_ a cada .txt dentro de cada subdirectorio de docs.
#!/bin/bash
base="docs"
for dir in "$base"/*/; do
for archivo in "$dir"*.txt; do
if [ -f "$archivo" ]; then
nombre=$(basename "$archivo" .txt)
mv "$archivo" "$dir/bak_$nombre.txt"
echo "Renombrado: $archivo → $dir/bak_$nombre.txt"
fi
done
done
Salida esperada (asumiendo que los archivos existen):
Renombrado: docs/proyecto1/informe.txt → docs/proyecto1/bak_informe.txt
Renombrado: docs/proyecto1/notas.txt → docs/proyecto1/bak_notas.txt
Renombrado: docs/proyecto2/resumen.txt → docs/proyecto2/bak_resumen.txt
Renombrado: docs/proyecto2/borrador.txt → docs/proyecto2/bak_borrador.txt
Bucles con control de flujo: break y continue
Dentro de cualquier bucle podés usar break para salir inmediatamente y continue para saltar al siguiente iteration.
Ejemplo: buscar el primer número par en una lista y salir
#!/bin/bash
numeros=(11 22 33 44 55)
for n in "${numeros[@]}" ; do
if [ $((n % 2)) -eq 0 ]; then
echo "Primer par encontrado: $n"
break
fi
done
Salida esperada:
Primer par encontrado: 22
Ejemplo: saltar los archivos ocultos al procesar un directorio
#!/bin/bash
for f in *; do
if [[ $f == .* ]]; then
continue # salta archivos que empiezan con punto
fi
echo "Procesando: $f"
done
Si el directorio contiene .bashrc, script.sh y README.md, la salida será:
Procesando: script.sh
Procesando: README.md
Bucles leyendo de entrada estándar (pipes y redirecciones)
Además de leer de un archivo con <, podés usar un while con un pipe para procesar la salida de otro comando.
Ejemplo: contar cuántas líneas contienen la palabra "error" en un log
#!/bin/bash
contador=0
grep -i error aplicacion.log | while read -r linea; do
((contador++))
done
echo "Líneas con 'error': $contador"
Supongamos que aplicacion.log tiene tres líneas con la palabra error (mayúscula o minúscula). La salida será:
Líneas con 'error': 3
Nota: Tené Importante: el
whiledentro de un pipe se ejecuta en un subshell, por lo que la variablecontadorno se conserva fuera del bucle si no se usa una redirección o se evita el pipe. Una alternativa es usar redirección de proceso o unmapfile.
Evitando el subshell con redirección
#!/bin/bash
contador=0
while IFS= read -r linea; do
((contador++))
done < <(grep -i error aplicacion.log)
echo "Líneas con 'error': $contador"
Ahora la variable se actualiza correctamente y la salida es la misma.
Bucles infinitos y cómo salir de ellos
Un bucle while true o until false crea un bucle infinito útil para daemons o scripts de monitoreo. Siempre debe haber una forma de salir, como una señal o una condición interna.
Ejemplo: script que muestra la fecha cada 10 segundos hasta que se presione Ctrl+C
#!/bin/bash
while true; do
date
sleep 10
done
Al ejecutarlo, verás algo como:
Thu Nov 2 14:23:01 UTC 2024
Thu Nov 2 14:23:11 UTC 2024
Thu Nov 2 14:23:21 UTC 2024
^C
El Ctrl+C envía la señal SIGINT y termina el bucle.
Bucles en scripts de inicialización y de limpieza
Los bucles son habituales en scripts de arranque (init.d, systemd service) y de mantenimiento (rotación de logs, backup).
Ejemplo: rotar logs de una aplicación manteniendo los últimos 7 días
#!/bin/bash
dir=/var/log/miapp
keep=7
# obtener lista de logs ordenados por fecha (más viejo primero)
logs=($(ls -t $dir/miapp.log.* 2>/dev/null))
# si hay más de $keep, borrar los excedentes
if [ ${#logs[@]} -gt $keep ]; then
for ((i=$keep; i<${#logs[@]}; i++)); do
echo "Borrando log antiguo: ${logs[i]}"
rm -f "${logs[i]}"
done
fi
Supongamos que en el directorio existen los archivos:
- miapp.log.2024-10-28
- miapp.log.2024-10-29
- miapp.log.2024-10-30
- miapp.log.2024-10-31
- miapp.log.2024-11-01
- miapp.log.2024-11-02
- miapp.log.2024-11-03
Con keep=7, no se borra nada. Si agregamos un octavo archivo miapp.log.2024-11-04, el bucle for borrará el más antiguo (2024-10-28) y mostrará:
Borrando log antiguo: /var/log/miapp/miapp.log.2024-10-28
Bucles con arrays asociativos (Bash 4+)
Aunque no son tan comunes, Bash permite arrays asociativos (clave‑valor). Iterar sobre ellos es útil para procesar configuraciones.
Ejemplo: imprimir variables de entorno seleccionadas
#!/bin/bash
declare -A env_vars
env_vars[HOME]="$HOME"
env_vars[USER]="$USER"
env_vars[SHELL]="$SHELL"
for clave in "${!env_vars[@]}" ; do
echo "$clave = ${env_vars[$clave]}"
done
Salida esperada (los valores dependerán de tu entorno):
HOME = /home/tuusuario
USER = tuusuario
SHELL = /bin/bash
Bucles y rendimiento: buenas prácticas
- Evita la expansión innecesaria de comandos dentro de la condición – si la condición no cambia, cálculala antes del bucle.
- Prefiere
forsobrewhilecuando iterás sobre una lista conocida – es más legible y evita errores de actualización de variables. - **Usa comillas alrededor de las cuales pueden quedar atrapadas en subshells.
- Usa
mapfileoreadarraypara leer líneas de un archivo en un array cuando necesitas acceder a los elementos múltiples veces sin volver a leer el archivo. - Limita el uso de subshells – los pipes crean subshells; si necesitás modificar variables externas, usa redirecciones de proceso (
< <( … )) o reasigná la salida a una variable y luego itere. - Controlá el crecimiento de variables – en bucles muy largos, acumular grandes cadenas puede consumir mucha memoria; considerá escribir directamente a un archivo o usar herramientas como
awkosedcuando corresponda. - Documentá la intención del bucle – un comentario breve al inicio ayuda a futuros mantenidores a entender si se trata de procesamiento, espera o reintentos.
Errores comunes y cómo evitarlos
- Olvidar incrementar o actualizar la variable de control en un
whileountil→ bucle infinito. Siempre verificá que la variable que participa en la condición cambie dentro del bloque. - No comandar correctamente los elementos con espacios – si los elementos de la lista pueden contener espacios, usá
forcon arrays y comodín"${array[@]}". Sin comillas, Bash divide por espacios y rompe los elementos. - Usar
[ $var -eq 0 ]sin inicializarvar– si la variable está vacía, Bash lanza un error "unary operator expected/. Inicializá siempre o usá[[ $var -eq 0 ]]que trata una variable vacía como cero en muchos casos. - Confundir
breakycontinue– recordá quebreaksale del bucle,continuesalta al próximo ciclo. Uncontinuemal puesto puede hacer que se omita lógica de limpieza esencial. - Asumir que un
while readprocesa la última línea si falta newline –readretorna falso cuando llega al final del archivo; si la última línea no termina en newline, aún se procesa. Pero si usás un pipe y el subshell termina temprano, podés perderla. La forma segura eswhile IFS= read -r linea || [ -n "$linea" ]; do. - Olvidar que
for i in {1..n}se expande antes de ejecutar el bucle – sinproviene de una variable, la sintaxis de rango no funciona; usáseqo un buclewhileen ese caso.
Conclusión
En este recorrido vimos los tres tipos de bucles que ofrece Bash:
for, ideal para iterar sobre listas estáticas, rangos y arrays.while, perfecto cuando la cantidad de iteraciones depende de una condición que se evalúa antes de cada paso.until, el inverso dewhile, útil para esperar a que algo ocurra.
Aprendimos a combinarlos con break y continue, a leer desde archivos y pipes, a evitar subshells no deseados y a aplicar buenas prácticas para que tus scripts sean legibles, eficientes y seguros.
Con los ejemplos de procesado de archivos, conteo, espera de procesos y generación de contraseñas, tenés una base sólida para automatizar tareas cotidianas en la línea de comandos. Ahora es tu turno: tomá alguno de los fragmentos, adaptálos a tu contexto y experimentá. Cada bucle que escribas te acercará más a dominar la automatización en Bash.
¡A scriptear!
DUGLAS MORENO
Comentarios (0)
Sé el primero en comentar.