Elaborazione di script Bash in numero limitato di comandi in parallelo


196

Ho uno script bash che assomiglia a questo:

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

Ma l'elaborazione di ogni riga fino al termine del comando, quindi il passaggio a quella successiva richiede molto tempo, voglio elaborare ad esempio 20 righe contemporaneamente, quindi quando hanno terminato vengono elaborate altre 20 righe.

Ho pensato di wget LINK1 >/dev/null 2>&1 &inviare il comando in background e continuare, ma ci sono 4000 righe qui, questo significa che avrò problemi di prestazioni, per non parlare del fatto che sono limitati in quanti processi dovrei iniziare contemporaneamente, quindi non è un buon idea.

Una soluzione a cui sto pensando in questo momento è verificare se uno dei comandi è ancora in esecuzione o meno, ad esempio dopo 20 righe posso aggiungere questo ciclo:

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

Naturalmente in questo caso dovrò aggiungere e alla fine della linea! Ma sento che questo non è il modo giusto per farlo.

Quindi, come posso effettivamente raggruppare ogni 20 righe insieme e aspettare che finiscano prima di passare alle 20 righe successive, questo script è generato dinamicamente in modo che io possa fare tutto ciò che voglio su di essa mentre viene generato, ma NON DEVE usa wget, era solo un esempio, quindi qualsiasi soluzione specifica per wget non mi farà del bene.


1
waitè la risposta giusta qui, ma la tua while [ $(ps …sarebbe molto meglio scritta while pkill -0 $KEYWORD…- usando proctools ... cioè, per motivi legittimi per verificare se un processo con un nome specifico è ancora in esecuzione.
Kojiro,

Penso che questa domanda dovrebbe essere riaperta. Il controllo di qualità "possibile duplicato" riguarda l'esecuzione in parallelo di un numero finito di programmi. Come 2-3 comandi. Questa domanda, tuttavia, è focalizzata sull'esecuzione di comandi, ad esempio in un ciclo. (vedi "ma ci sono 4000 righe").
VasiliNovikov,

@VasyaNovikov Hai letto tutte le risposte sia a questa domanda che al duplicato? Ogni singola risposta a questa domanda qui, può anche essere trovata nelle risposte alla domanda duplicata. Questa è precisamente la definizione di una domanda duplicata. Non fa assolutamente alcuna differenza se si eseguono o meno i comandi in un ciclo.
robinCTS,

@robinCTS ci sono incroci, ma le domande stesse sono diverse. Inoltre, 6 delle risposte più popolari sul QA collegato riguardano solo 2 processi.
Vasili Novikov,

2
Raccomando di riaprire questa domanda perché la sua risposta è più chiara, più pulita, migliore e molto più votata rispetto alla risposta alla domanda collegata, sebbene sia più recente di tre anni.
Dan Nissenbaum,

Risposte:


331

Usa il waitbuilt-in:

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

Per l'esempio sopra, 4 processi process1... process4verrebbero avviati in background e la shell aspetterebbe che vengano completati prima di iniziare il set successivo.

Dal manuale GNU :

wait [jobspec or pid ...]

Attendere fino a quando il processo figlio specificato da ciascun pid ID processo o specifica processo jobpec si chiude e restituisce lo stato di uscita dell'ultimo comando atteso. Se viene fornita una specifica lavoro, vengono attesi tutti i processi nel lavoro. Se non viene fornito alcun argomento, vengono attesi tutti i processi figlio attualmente attivi e lo stato di restituzione è zero. Se né jobspec né pid specificano un processo figlio attivo della shell, lo stato di ritorno è 127.


14
Quindi, in praticai=0; waitevery=4; for link in "${links[@]}"; do wget "$link" & (( i++%waitevery==0 )) && wait; done >/dev/null 2>&1
Kojiro

18
A meno che tu non sia sicuro che ogni processo finirà nello stesso momento, questa è una cattiva idea. È necessario avviare nuovi lavori per mantenere gli attuali lavori totali a un certo limite .... parallela è la risposta.
visto il

1
C'è un modo per farlo in un ciclo?
Domini:

Ho provato questo, ma sembra che le assegnazioni di variabili eseguite in un blocco non siano disponibili nel blocco successivo. Questo perché sono processi separati? C'è un modo per comunicare le variabili al processo principale?
Bobby,

97

Vedi parallelo . La sua sintassi è simile a xargs, ma esegue i comandi in parallelo.


13
Questo è meglio dell'uso wait, poiché si occupa di avviare nuovi lavori come quelli precedenti completi, invece di attendere il completamento di un intero batch prima di iniziare il successivo.
Chepner,

5
Ad esempio, se hai un elenco di collegamenti in un file, puoi fare ciò cat list_of_links.txt | parallel -j 4 wget {}che farà wgetfunzionare quattro s alla volta.
Mr. Llama,

5
C'è un nuovo bambino in città chiamato Pexec che è un sostituto per parallel.
slashsbin,

2
Fornire un esempio sarebbe più utile
jterm

1
parallel --jobs 4 < list_of_commands.sh, dove list_of_commands.sh è un file con un singolo comando (ad esempio wget LINK1, nota senza il &) su ogni riga. Potrebbe essere necessario farlo CTRL+Ze bgdopo lasciarlo in esecuzione in background.
weiji14

71

In effetti, xargs puoi eseguire comandi in parallelo per te. C'è una speciale -P max_procsopzione da riga di comando per questo. Vedere man xargs.


2
+100 questo è fantastico dal momento che è incorporato e molto semplice da usare e può essere fatto in una sola riga
Clay

Ottimo da usare per piccoli contenitori, poiché non sono necessari pacchetti / dipendenze extra!
Marco Roy,

1
Vedi questa domanda per esempi: stackoverflow.com/questions/28357997/…
Marco Roy

7

È possibile eseguire 20 processi e utilizzare il comando:

wait

Lo script attenderà e continuerà al termine di tutti i processi in background.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.