BashShellDepuraciónProcesosAutomatizaciónFiabilidad

Seis horas esperando a un proceso que era él mismo

Publicado el 2026-08-18 · Xiliux

Un bucle de espera estuvo seis horas y doce minutos girando sobre un sleep 3. No estaba esperando a nada: se estaba esperando a sí mismo.

El código era este, y a primera vista no tiene nada raro:

until ! pgrep -f "respaldo-nocturno" > /dev/null; do
  sleep 3
done
echo "el respaldo terminó"

Léelo otra vez. La idea es «espera mientras exista un proceso cuyo nombre contenga respaldo-nocturno». Impecable, salvo por un detalle.

El patrón está escrito en el propio bucle

pgrep -f compara el patrón contra la línea de órdenes completa de cada proceso del sistema.

¿Y qué hay en la línea de órdenes del shell que ejecuta ese bucle? Exactamente la cadena respaldo-nocturno, porque está escrita ahí dentro.

Así que pgrep encuentra un proceso: el bash del propio bucle. Devuelve éxito. La negación es falsa. El bucle sigue. Y seguirá mientras el shell exista, que es precisamente mientras el bucle siga.

La condición no puede volverse falsa nunca. Es una espera cerrada sobre sí misma.

Lo peor no es el fallo: es que no da error

Desde fuera, este bucle es indistinguible de un trabajo que tarda. El shell está vivo, no consume CPU, no escribe nada y no falla. Ningún mecanismo lo señala.

Lo descubrí mirando la lista de procesos por otra cosa. Seis horas y doce minutos. Si nadie mira, un bucle así puede girar días.

Esta es la categoría de defecto más cara que existe: la que no se queja. Un error se ve en un minuto; una espera imposible se ve cuando alguien pasa por delante.

Las tres formas correctas

1. pgrep -x sobre el nombre del ejecutable. Si lo que esperas es un programa concreto, no busques en la línea de órdenes: busca el nombre exacto del proceso.

until ! pgrep -x "respaldo" > /dev/null; do sleep 3; done

2. El truco del corchete. Un clásico que resuelve el caso sin cambiar de herramienta:

until ! pgrep -f "[r]espaldo-nocturno" > /dev/null; do sleep 3; done

La expresión sigue casando con el proceso real, pero la cadena que queda escrita en la línea de órdenes del shell lleva corchetes y ya no coincide consigo misma.

3. Esperar al artefacto, no al proceso. Muchas veces la espera correcta no mira procesos en absoluto, sino el resultado que el trabajo deja escrito:

until grep -q '^RESPALDO OK' "$LOG"; do sleep 3; done

Y si el proceso lo lanzaste tú, no hay nada que buscar: wait $PID.

Y el tope, que no es opcional

Cualquiera de las tres sigue siendo una espera sin límite. Añade siempre un techo:

fin=$(( $(date +%s) + 600 ))
until ! pgrep -x "respaldo" > /dev/null || [ "$(date +%s)" -gt "$fin" ]; do
  sleep 3
done

Diez minutos, y si no pasó, pasó otra cosa. El tope no está para el caso normal: está para convertir un cuelgue silencioso en algo que se ve.

La pregunta que evita todos estos casos

Antes de escribir cualquier until o while, una sola pregunta:

¿Qué proceso, concretamente, va a hacer verdadera esta condición?

Si la respuesta no es un proceso que ya está corriendo, la espera es imposible. Y si la respuesta es «el que estoy buscando por su línea de órdenes», mira dos veces si ese patrón no está escrito también en el shell que hace la pregunta.

Coda: el guardián que cazó este mismo artículo

Escribí un hook que rechaza esta construcción antes de ejecutarla. Al redactar este texto, el hook bloqueó el comando que lo estaba guardando: el patrón del ejemplo viajaba dentro del propio comando y el guardián no distingue un bucle real de una cadena de texto.

Es un falso positivo, y es el mejor que me ha pasado. Un detector que solo mira la forma de la orden no puede saber si esa orden se va a ejecutar o se va a imprimir en un blog.

Lo que me llevo

  1. Buscar por línea de órdenes se encuentra a sí mismo cuando el patrón vive en el bucle. Usa -x, el corchete, o espera al artefacto.
  2. Una espera imposible no da error. Se ve igual que un trabajo lento, y por eso dura horas.
  3. Toda espera lleva tope de tiempo. Sin él, el fallo no tiene forma de manifestarse.
  4. Nombra a quien va a terminar la espera antes de escribirla. Si no puedes nombrarlo, no la escribas.

Preguntas frecuentes

¿Por qué la búsqueda se encuentra a sí misma?

Porque buscar por línea de órdenes compara el patrón contra la línea completa de cada proceso, y el shell que ejecuta el bucle tiene ese patrón escrito literalmente en la suya. El shell aparece en el listado, la búsqueda devuelve éxito, y la condición del bucle nunca se vuelve falsa.

¿Qué es el truco del corchete?

Escribir el patrón como [p]atron en vez de patron. La expresión sigue casando con el proceso real, pero la cadena literal que queda en la línea de órdenes del shell contiene corchetes y ya no coincide consigo misma. Es un clásico de la línea de órdenes y resuelve el caso sin cambiar de herramienta.

¿Por qué no salta ninguna alarma?

Porque desde fuera una espera imposible es indistinguible de un trabajo que tarda. El shell está vivo, no consume CPU y no escribe nada. Sin un tope de tiempo, puede girar horas o días sin que ningún mecanismo lo señale.

¿Cuál es la regla general antes de escribir una espera?

Preguntarse qué proceso concreto va a hacer verdadera esa condición. Si la respuesta no es un proceso que ya está corriendo, la espera es imposible. Y toda espera lleva tope de tiempo, para que el fallo se manifieste en vez de acumularse en silencio.

← Ver más artículosCotizar un proyecto