Bucles en Bash: for, while y until con ejemplos prácticos

DUGLAS MORENODUGLAS MORENO 👁 24

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 for y sus variantes (rangos y arrays).
  • Cómo usar while y until para 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 while dentro de un pipe se ejecuta en un subshell, por lo que la variable contador no 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 un mapfile.

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

  1. Evita la expansión innecesaria de comandos dentro de la condición – si la condición no cambia, cálculala antes del bucle.
  2. Prefiere for sobre while cuando iterás sobre una lista conocida – es más legible y evita errores de actualización de variables.
  3. **Usa comillas alrededor de las cuales pueden quedar atrapadas en subshells.
  4. Usa mapfile o readarray para leer líneas de un archivo en un array cuando necesitas acceder a los elementos múltiples veces sin volver a leer el archivo.
  5. 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.
  6. 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 awk o sed cuando corresponda.
  7. 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 while o until → 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á for con arrays y comodín "${array[@]}". Sin comillas, Bash divide por espacios y rompe los elementos.
  • Usar [ $var -eq 0 ] sin inicializar var – 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 break y continue – recordá que break sale del bucle, continue salta al próximo ciclo. Un continue mal puesto puede hacer que se omita lógica de limpieza esencial.
  • Asumir que un while read procesa la última línea si falta newlineread retorna 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 es while IFS= read -r linea || [ -n "$linea" ]; do.
  • Olvidar que for i in {1..n} se expande antes de ejecutar el bucle – si n proviene de una variable, la sintaxis de rango no funciona; usá seq o un bucle while en 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 de while, ú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!

Comentarios (0)

Sé el primero en comentar.

Dejá tu comentario