En Bash, lanzar tareas en segundo plano con &
es una forma directa de ganar concurrencia: descargar varios ficheros a la vez, convertir lotes en paralelo, arrancar servicios auxiliares mientras se prepara el entorno… El problema viene después: ¿cuándo seguir? Si el guion continúa sin asegurarse de que esas tareas han terminado, aparecen condiciones de carrera y fallos intermitentes difíciles de depurar.
Ahí entra wait
. Este comando bloquea la ejecución del script hasta que finaliza un proceso en segundo plano (uno concreto o todos). Es simple, pero marca la diferencia entre una automatización frágil y otra determinista.
A continuación, una guía clara para usar wait
en casos reales: un proceso, varios procesos, esperar a cualquiera, capturar códigos de salida, y errores comunes a evitar.
Requisitos
- Un sistema GNU/Linux o terminal con Bash.
- Conocimientos básicos de shell scripting.
- Poder crear y ejecutar scripts
.sh
.
Sintaxis y conceptos clave
wait [PID ...]
Lenguaje del código: CSS (css)
wait
sin argumentos: espera a todos los trabajos en segundo plano.wait PID
: espera solo al proceso con ese PID.$!
: contiene el PID del último comando lanzado al fondo.$?
: traswait
, guarda el código de salida del proceso esperado.wait -n
: retorna cuando finaliza cualquiera de los trabajos en segundo plano (el primero que acabe).
Nota: si
wait
recibe un PID inexistente, devuelve 127.
Caso 1: un único proceso (esperar a todos)
Cuando lanzas algo con &
, el script sigue. Para pausar hasta que termine:
wait-single.sh
#!/bin/bash
echo "Lanzando proceso en segundo plano..."
sleep 5 &
echo "Esperando a que termine..."
wait
echo "Proceso finalizado."
Lenguaje del código: PHP (php)
chmod +x wait-single.sh
./wait-single.sh
sleep 5 &
va al fondo.wait
sin argumentos bloquea hasta que terminen todos los trabajos en segundo plano (en este ejemplo, solo hay uno).
Caso 2: un proceso concreto (esperar por PID)
Si tienes varios trabajos y quieres esperar solo por uno:
wait-pid.sh
#!/bin/bash
sleep 3 &
pid=$!
echo "Esperando al PID $pid..."
wait "$pid"
echo "El proceso $pid ha terminado con estado $?"
Lenguaje del código: PHP (php)
chmod +x wait-pid.sh
./wait-pid.sh
- Guardas el PID con
$!
. wait "$pid"
espera solo a ese proceso.- Después de
wait
,$?
es el exit code del proceso esperado.
Caso 3: varios procesos (espera ordenada por PID)
Lanza varios trabajos y espera cada uno por su PID para controlar el flujo:
wait-multiple.sh
#!/bin/bash
echo "Lanzando procesos..."
sleep 3 & pid1=$!
sleep 2 & pid2=$!
wait "$pid1"
echo "Proceso 1 (PID $pid1) finalizado: $?"
wait "$pid2"
echo "Proceso 2 (PID $pid2) finalizado: $?"
Lenguaje del código: PHP (php)
- Puedes decidir qué tarea debe terminar antes de continuar con el resto.
- Cada
wait
actualiza$?
con el código de la tarea correspondiente.
Caso 4: esperar a todos sin listar PIDs
Si te basta con que acaben todos:
wait-all.sh
#!/bin/bash
echo "Lanzando tareas..."
sleep 2 &
sleep 4 &
wait
echo "Todas las tareas han terminado."
Lenguaje del código: PHP (php)
- Ideal cuando no te importa el orden, solo no seguir hasta que no quede nada en segundo plano.
Caso 5: wait -n
(cuando acabe cualquiera)
-n
hace que wait
retorne en cuanto termine el primer trabajo de los que queden. Muy útil para reaccionar a la tarea que finalice antes (por ejemplo, para empezar a procesar resultados parciales).
wait-any.sh
#!/bin/bash
sleep 3 &
sleep 5 &
wait -n
echo "Al menos un trabajo ha terminado."
Lenguaje del código: PHP (php)
Puedes combinarlo con un bucle para ir atendiendo terminaciones a medida que ocurren:
#!/bin/bash
sleep 1 & p1=$!
sleep 4 & p2=$!
sleep 2 & p3=$!
# Mientras queden jobs en segundo plano…
while jobs -rp >/dev/null; do
wait -n
rc=$?
echo "Terminó un job con estado $rc"
done
echo "No quedan trabajos."
Lenguaje del código: PHP (php)
jobs -rp
lista PIDs de trabajos activos; el bucle sigue mientras haya alguno.- Cada
wait -n
devuelve el estado de uno de los procesos que acaban.
Capturar y asociar códigos de salida a cada PID
Si te importa saber qué tarea falló, guarda los PIDs y sus nombres/etiquetas en arrays:
#!/bin/bash
declare -a PIDS NAMES
run() { "$@" & PIDS+=($!); NAMES+=("$*"); }
run curl -sS https://example.com/a
run curl -sS https://example.com/b
run curl -sS https://example.com/c
for i in "${!PIDS[@]}"; do
pid=${PIDS[$i]}
name=${NAMES[$i]}
if wait "$pid"; then
echo "[OK] $name (PID $pid)"
else
echo "[FAIL] $name (PID $pid) exit=$?"
fi
done
Lenguaje del código: PHP (php)
wait "$pid"
devuelve 0 si el proceso terminó con éxito; en caso contrario, devuelve su exit code.- Esto permite tratar fallos de manera granular.
Esperar por jobspec (%1
, %2
)
Bash permite referirse a trabajos por jobspec (como en fg
/bg
), p. ej. %1
. También funciona con wait
:
#!/bin/bash
sleep 3 &
sleep 5 &
jobs
wait %1 # espera al primer job
echo "Terminó %1"
wait # espera al que queda
echo "Todos listos"
Lenguaje del código: PHP (php)
En scripts, el control por PID suele ser más robusto que por
%n
.
Errores comunes y cómo evitarlos
- Olvidar capturar el PID inmediatamente
Si haces otras llamadas entrecomando &
ypid=$!
, corres el riesgo de guardar otro PID. Captura$!
en la siguiente línea. - Confundir
wait
ysleep
sleep
pausa un tiempo fijo;wait
sincroniza con procesos en segundo plano. Son complementarios, no equivalentes. - Pipelines y PIDs
Encmd1 | cmd2 &
, el último proceso del pipeline (a menudocmd2
) puede no ser el que te interesa. Considera subshells y redirecciones:{ cmd1 | cmd2; } & pid=$! wait "$pid"
O usaset -o pipefail
si te preocupa el código de salida del pipeline completo. - Suposiciones sobre
jobs
en scripts
El control de trabajos (job control) puede estar desactivado en scripts no interactivos. Trabajar con PIDs es más fiable. - Perder estados de salida
wait
actualiza$?
. Si lo necesitas, guárdalo enseguida en otra variable.
Patrón práctico: límite de concurrencia con wait
Sin GNU Parallel, puedes limitar el número de trabajos simultáneos con unas pocas líneas:
#!/bin/bash
max=4
pids=()
for f in *.tar.gz; do
{ tar -tzf "$f" >/dev/null; } & pids+=($!)
# Si alcanzas el límite, espera a que termine cualquiera
if (( ${#pids[@]} >= max )); then
wait -n
# depura array: elimina PIDs ya terminados
alive=()
for p in "${pids[@]}"; do kill -0 "$p" 2>/dev/null && alive+=("$p"); done
pids=("${alive[@]}")
fi
done
# Espera a los que queden
wait
echo "Verificaciones completadas."
Lenguaje del código: PHP (php)
Este patrón mantiene hasta max
trabajos activos, añadiendo nuevos conforme otros finalizan.
Buenas prácticas
- Captura PIDs inmediatamente con
$!
. - Comprueba códigos de salida y maneja errores.
- Usa
wait -n
si necesitas reactividad temprana. - Prefiere PIDs a jobspec en scripts.
- Documenta tu modelo de concurrencia: quién lanza, quién espera y por qué.
Conclusión
wait
es una pieza pequeña que resuelve un problema grande: coordinar procesos en segundo plano sin trampas temporales. Con él puedes:
- Evitar condiciones de carrera.
- Sincronizar etapas de un pipeline.
- Hacer scripts predecibles incluso con varias tareas en paralelo.
Combina wait
con $!
y -n
para un control fino, captura los exit codes y, si necesitas más sofisticación (colas, reintentos, timeouts), apóyate en estos cimientos: la estabilidad empieza por saber cuándo seguir.
Preguntas frecuentes (FAQ)
¿Qué devuelve wait
?
El código de salida del proceso esperado (0 en éxito; distinto de 0 si falló). Si el PID no existe, wait
devuelve 127. Con wait -n
, devuelve el estado del primer trabajo que termine.
¿Puedo establecer un timeout a wait
?wait
no tiene timeout. Puedes combinarlo con timeout
:
timeout 10s bash -c 'wait '"$pid"
Lenguaje del código: JavaScript (javascript)
o con vigilancia periódica (kill -0 "$pid"
) y sleep
en un bucle.
¿wait
sirve con procesos nieto (creados por el hijo)?wait
solo espera procesos lanzados por tu shell. Para jerarquías complejas, espera al PID raíz que lanzaste (el que conoces) o usa una estrategia de supervisión adecuada.
¿Es mejor wait
o usar xargs -P
/GNU Parallel?
Depende. wait
te da control nativo y fino; xargs -P
o GNU Parallel son excelentes para procesar listas con límite de concurrencia y menos código, a costa de menos control por proceso en el propio script. Puedes combinarlos según el caso.