Script de Bash que procesa un número limitado de comandos en paralelo

196

Tengo un script bash que se ve así:

#!/bin/bash
wget LINK1 >/dev/null 2>&1
wget LINK2 >/dev/null 2>&1
wget LINK3 >/dev/null 2>&1
wget LINK4 >/dev/null 2>&1
# ..
# ..
wget LINK4000 >/dev/null 2>&1

Pero procesar cada línea hasta que finalice el comando y luego pasar a la siguiente lleva mucho tiempo, quiero procesar, por ejemplo, 20 líneas a la vez y luego, cuando están terminadas, se procesan otras 20 líneas.

Pensé wget LINK1 >/dev/null 2>&1 &en enviar el comando a un segundo plano y continuar, pero hay 4000 líneas aquí, lo que significa que tendré problemas de rendimiento, sin mencionar la limitación en la cantidad de procesos que debo iniciar al mismo tiempo, por lo que no es un buen idea.

Una solución en la que estoy pensando ahora es verificar si uno de los comandos aún se está ejecutando o no, por ejemplo, después de 20 líneas puedo agregar este bucle:

while [  $(ps -ef | grep KEYWORD | grep -v grep | wc -l) -gt 0 ]; do
sleep 1
done

¡Por supuesto que en este caso tendré que agregar y al final de la línea! Pero siento que esta no es la forma correcta de hacerlo.

Entonces, ¿cómo puedo agrupar cada una de las 20 líneas y esperar a que terminen antes de pasar a las siguientes 20 líneas? use wget, fue solo un ejemplo, por lo que cualquier solución específica de wget no me servirá de nada.

AL-Kateb
fuente
1
waites la respuesta correcta aquí, pero while [ $(ps …sería mucho mejor escribirlo while pkill -0 $KEYWORD…usando proctools ... es decir, por razones legítimas para verificar si un proceso con un nombre específico aún se está ejecutando.
kojiro
Creo que esta pregunta debería reabrirse. El control de calidad "posible duplicado" se trata de ejecutar un número finito de programas en paralelo. Como 2-3 comandos. Sin embargo, esta pregunta se centra en ejecutar comandos, por ejemplo, en un bucle. (ver "pero hay 4000 líneas").
VasiliNovikov
@VasyaNovikov ¿Has leído todas las respuestas a esta pregunta y al duplicado? Cada respuesta a esta pregunta aquí también se puede encontrar en las respuestas a la pregunta duplicada. Esa es precisamente la definición de una pregunta duplicada. No hace absolutamente ninguna diferencia si está ejecutando o no los comandos en un bucle.
robinCTS
@robinCTS hay intersecciones, pero las preguntas en sí mismas son diferentes. Además, 6 de las respuestas más populares en el control de calidad vinculado tratan solo con 2 procesos.
VasiliNovikov
2
Recomiendo reabrir esta pregunta porque su respuesta es más clara, más limpia, mejor y mucho más votada que la respuesta de la pregunta vinculada, aunque es tres años más reciente.
Dan Nissenbaum

Respuestas:

331

Use el waitincorporado:

process1 &
process2 &
process3 &
process4 &
wait
process5 &
process6 &
process7 &
process8 &
wait

Para el ejemplo anterior, 4 procesos process1... process4se iniciarían en segundo plano y el shell esperaría hasta que se completen antes de comenzar el siguiente conjunto.

Del manual de GNU :

wait [jobspec or pid ...]

Espere hasta que el proceso secundario especificado por cada ID de proceso pid o especificación de trabajo finalice y especifique el estado de salida del último comando esperado. Si se proporciona una especificación de trabajo, se esperan todos los procesos en el trabajo. Si no se proporcionan argumentos, se esperan todos los procesos secundarios actualmente activos y el estado de retorno es cero. Si ni jobspec ni pid especifican un proceso secundario activo del shell, el estado de retorno es 127.

devnull
fuente
14
Básicamentei=0; waitevery=4; for link in "${links[@]}"; do wget "$link" & (( i++%waitevery==0 )) && wait; done >/dev/null 2>&1
kojiro el
18
A menos que esté seguro de que cada proceso terminará exactamente al mismo tiempo, esta es una mala idea. Debe iniciar nuevos trabajos para mantener los trabajos totales actuales en un cierto límite ... paralela es la respuesta.
rsaw
1
¿Hay alguna manera de hacer esto en un bucle?
DominiosDestacado el
He intentado esto, pero parece que las asignaciones variables realizadas en un bloque no están disponibles en el siguiente bloque. ¿Es esto porque son procesos separados? ¿Hay alguna manera de comunicar las variables al proceso principal?
Bobby
97

Ver paralelo . Su sintaxis es similar a xargs, pero ejecuta los comandos en paralelo.

choroba
fuente
13
Esto es mejor que usar wait, ya que se encarga de comenzar nuevos trabajos a medida que se completan los viejos, en lugar de esperar a que termine un lote completo antes de comenzar el siguiente.
chepner
55
Por ejemplo, si tiene la lista de enlaces en un archivo, puede hacer cat list_of_links.txt | parallel -j 4 wget {}lo que mantendrá cuatro wgets funcionando a la vez.
Sr. Llama
55
Hay un nuevo chico en la ciudad llamado pexec que es un reemplazo para parallel.
slashsbin el
2
Proporcionar un ejemplo sería más útil
jterm
1
parallel --jobs 4 < list_of_commands.sh, donde list_of_commands.sh es un archivo con un solo comando (por ejemplo wget LINK1, nota sin el &) en cada línea. Puede que tenga que hacer CTRL+Zy bgdespués dejarlo ejecutándose en segundo plano.
weiji14
71

De hecho, xargs puede ejecutar comandos en paralelo para usted. Hay una -P max_procsopción especial de línea de comandos para eso. Ver man xargs.

Vader B
fuente
2
+100 esto es genial ya que está integrado y es muy fácil de usar y se puede hacer de una sola vez
Clay
Ideal para usar en contenedores pequeños, ya que no se necesitan paquetes / dependencias adicionales.
Marco Roy
1
Consulte esta pregunta para ver ejemplos: stackoverflow.com/questions/28357997/…
Marco Roy
7

Puede ejecutar 20 procesos y usar el comando:

wait

Su secuencia de comandos esperará y continuará cuando finalicen todos sus trabajos en segundo plano.

Binpix
fuente