Script Bash traitant un nombre limité de commandes en parallèle

196

J'ai un script bash qui ressemble à ceci:

#!/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

Mais le traitement de chaque ligne jusqu'à la fin de la commande, puis le passage à la suivante prennent beaucoup de temps, je veux par exemple traiter 20 lignes à la fois, puis quand elles sont terminées, 20 autres lignes sont traitées.

J'ai pensé wget LINK1 >/dev/null 2>&1 &envoyer la commande en arrière-plan et continuer, mais il y a 4000 lignes ici, cela signifie que j'aurai des problèmes de performances, sans parler du nombre limité de processus que je devrais démarrer en même temps, donc ce n'est pas une bonne chose idée.

Une solution à laquelle je pense en ce moment est de vérifier si l'une des commandes est toujours en cours d'exécution ou non, par exemple après 20 lignes, je peux ajouter cette boucle:

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

Bien sûr, dans ce cas, je devrai ajouter & à la fin de la ligne! Mais je sens que ce n'est pas la bonne façon de procéder.

Alors, comment puis-je réellement regrouper chaque 20 lignes et attendre qu'elles se terminent avant de passer aux 20 lignes suivantes, ce script est généré dynamiquement afin que je puisse faire les calculs que je veux dessus pendant qu'il est généré, mais il N'EST PAS obligé utilisez wget, c'était juste un exemple, donc toute solution spécifique à wget ne me fera aucun bien.

AL-Kateb
la source
1
waitest la bonne réponse ici, mais votre while [ $(ps …serait beaucoup mieux écrit while pkill -0 $KEYWORD…- en utilisant proctools … c'est-à-dire, pour des raisons légitimes, de vérifier si un processus avec un nom spécifique est toujours en cours d'exécution.
kojiro
Je pense que cette question devrait être rouverte. Le contrôle qualité "double possible" consiste à exécuter un nombre fini de programmes en parallèle. Comme 2-3 commandes. Cette question, cependant, se concentre sur l'exécution de commandes, par exemple dans une boucle. (voir "mais il y a 4000 lignes").
VasiliNovikov
@VasyaNovikov Avez-vous lu toutes les réponses à cette question et au double? Chaque réponse à cette question ici, peut également être trouvée dans les réponses à la question en double. C'est précisément la définition d'une question en double. Peu importe que vous exécutiez ou non les commandes en boucle.
robinCTS
@robinCTS il y a des intersections, mais les questions elles-mêmes sont différentes. En outre, 6 des réponses les plus populaires sur le contrôle qualité lié ne concernent que 2 processus.
VasiliNovikov
2
Je recommande de rouvrir cette question parce que sa réponse est plus claire, plus propre, meilleure et beaucoup plus valorisée que la réponse à la question liée, bien qu'elle soit trois ans plus récente.
Dan Nissenbaum

Réponses:

331

Utilisez le waitintégré:

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

Pour l'exemple ci-dessus, 4 processus process1... process4seraient démarrés en arrière-plan, et le shell attendrait qu'ils soient terminés avant de démarrer l'ensemble suivant.

Dans le manuel GNU :

wait [jobspec or pid ...]

Attendez la fin du processus enfant spécifié par chaque identifiant de processus pid ou spécification de travail jobspec et retournez l'état de sortie de la dernière commande attendue. Si une spécification de travail est donnée, tous les processus du travail sont attendus. Si aucun argument n'est donné, tous les processus enfants actuellement actifs sont attendus et l'état de retour est zéro. Si ni jobspec ni pid ne spécifient un processus enfant actif du shell, l'état de retour est 127.

devnull
la source
14
Donc, fondamentalementi=0; waitevery=4; for link in "${links[@]}"; do wget "$link" & (( i++%waitevery==0 )) && wait; done >/dev/null 2>&1
kojiro
18
À moins que vous ne soyez sûr que chaque processus se terminera en même temps, c'est une mauvaise idée. Vous devez créer de nouveaux emplois pour maintenir le total actuel des emplois à un certain plafond ... parallèle est la réponse.
rsaw
1
Existe-t-il un moyen de le faire en boucle?
DomainsFeatured
J'ai essayé cela, mais il semble que les affectations de variables effectuées dans un bloc ne soient pas disponibles dans le bloc suivant. Est-ce parce que ce sont des processus distincts? Existe-t-il un moyen de communiquer les variables au processus principal?
Bobby
97

Voir parallèle . Sa syntaxe est similaire à xargs, mais il exécute les commandes en parallèle.

choroba
la source
13
C'est mieux que d'utiliser wait, car il prend soin de démarrer de nouveaux travaux lorsque les anciens sont terminés, au lieu d'attendre la fin d'un lot avant de commencer le suivant.
chepner
5
Par exemple, si vous avez la liste des liens dans un fichier, vous pouvez faire cat list_of_links.txt | parallel -j 4 wget {}ce qui gardera quatre wgets en cours d'exécution à la fois.
M. Llama
5
Il y a un nouveau gamin en ville appelé pexec qui remplace parallel.
slashsbin
2
Donner un exemple serait plus utile
jterm
1
parallel --jobs 4 < list_of_commands.sh, où list_of_commands.sh est un fichier avec une seule commande (par exemple wget LINK1, note sans le &) sur chaque ligne. Peut-être besoin de le faire CTRL+Zet bgaprès de le laisser fonctionner en arrière-plan.
weiji14
71

En fait, xargs peut exécuter des commandes en parallèle pour vous. Il existe une -P max_procsoption de ligne de commande spéciale pour cela. Tu vois man xargs.

Vader B
la source
2
+100 c'est génial car il est intégré et très simple à utiliser et peut être fait en une ligne
Clay
Idéal pour les petits conteneurs, car aucun paquet / dépendance supplémentaire n'est nécessaire!
Marco Roy
1
Voir cette question pour des exemples: stackoverflow.com/questions/28357997/…
Marco Roy
7

Vous pouvez exécuter 20 processus et utiliser la commande:

wait

Votre script attendra et continuera lorsque tous vos travaux en arrière-plan seront terminés.

Binpix
la source