Przetwarzanie skryptu Bash równolegle ograniczonej liczby poleceń

196

Mam skrypt bash, który wygląda następująco:

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

Ale przetwarzanie każdej linii, aż do zakończenia polecenia, a następnie przejście do następnej jest bardzo czasochłonne, chcę przetworzyć na przykład 20 linii na raz, a po ich zakończeniu przetwarzanych jest kolejnych 20 linii.

Myślałem o wget LINK1 >/dev/null 2>&1 &wysłaniu polecenia w tle i kontynuowaniu, ale jest tutaj 4000 linii, co oznacza, że ​​będę mieć problemy z wydajnością, nie wspominając o ograniczeniu liczby procesów, które powinienem uruchomić w tym samym czasie, więc to nie jest dobre pomysł.

Jednym z rozwiązań, o których teraz myślę, jest sprawdzenie, czy jedno z poleceń nadal działa, na przykład po 20 wierszach mogę dodać tę pętlę:

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

Oczywiście w tym przypadku będę musiał dołączyć i na końcu linii! Ale czuję, że to nie jest właściwy sposób, aby to zrobić.

Jak więc właściwie pogrupować każde 20 wierszy razem i poczekać, aż zakończą się, zanim przejdą do następnych 20 wierszy, ten skrypt jest generowany dynamicznie, więc mogę robić dowolną matematykę na nim podczas generowania, ale NIE musi to robić użyj wget, to był tylko przykład, więc każde rozwiązanie, które jest specyficzne dla wget, nic mi nie da.

AL-Kateb
źródło
1
waitjest tutaj poprawną odpowiedzią, ale while [ $(ps …lepiej napisać while pkill -0 $KEYWORD…- używając proctools … czyli z uzasadnionych powodów, aby sprawdzić, czy proces o określonej nazwie nadal działa.
kojiro
Myślę, że to pytanie powinno zostać ponownie otwarte. „Możliwy duplikat” QA polega na równoległym uruchamianiu skończonej liczby programów. Jak 2-3 polecenia. To pytanie koncentruje się jednak na uruchamianiu poleceń np. W pętli. (patrz „ale jest 4000 linii”).
VasiliNovikov
@VasyaNovikov Czy przeczytałeś wszystkie odpowiedzi na to pytanie i duplikat? Każda odpowiedź tutaj na to pytanie znajduje się również w odpowiedzi na zduplikowane pytanie. To jest dokładnie definicja duplikatu pytania. Nie ma absolutnie żadnej różnicy, czy polecenia są uruchamiane w pętli.
robinCTS
@robinCTS są skrzyżowania, ale same pytania są różne. Ponadto 6 najpopularniejszych odpowiedzi na temat powiązanej kontroli jakości dotyczy tylko 2 procesów.
VasiliNovikov
2
Polecam ponownie otworzyć to pytanie, ponieważ jego odpowiedź jest jaśniejsza, czystsza, lepsza i znacznie bardziej pozytywnie oceniona niż odpowiedź na pytanie powiązane, choć jest trzy lata później.
Dan Nissenbaum,

Odpowiedzi:

331

Użyj waitwbudowanego:

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

W powyższym przykładzie 4 procesy process1... process4zostałyby uruchomione w tle, a powłoka poczekałaby na ich zakończenie przed uruchomieniem następnego zestawu.

Z podręcznika GNU :

wait [jobspec or pid ...]

Poczekaj, aż proces potomny określony przez każdy identyfikator procesu pid lub specyfikacja zadania specyfikacja zadania zakończy działanie i zwróć status wyjścia ostatniej oczekującej komendy. Jeśli podano specyfikację zadania, wszystkie procesy w zadaniu są oczekiwane. Jeśli nie podano żadnych argumentów, czekane są wszystkie aktualnie aktywne procesy potomne, a zwracany status to zero. Jeśli ani specyfika zadania, ani pid nie określają aktywnego procesu potomnego powłoki, zwracany status to 127.

diabelnie
źródło
14
Więc w zasadziei=0; waitevery=4; for link in "${links[@]}"; do wget "$link" & (( i++%waitevery==0 )) && wait; done >/dev/null 2>&1
kojiro
18
Jest to zły pomysł, chyba że masz pewność, że każdy proces zakończy się dokładnie w tym samym czasie. Musisz uruchomić nowe zadania, aby utrzymać bieżącą liczbę zadań na pewnym limicie .... równolegle jest odpowiedzią.
rsaw
1
Czy można to zrobić w pętli?
DomainsFeatured 13.09.16
Próbowałem tego, ale wydaje się, że przypisania zmiennych wykonane w jednym bloku nie są dostępne w następnym bloku. Czy to dlatego, że są to osobne procesy? Czy istnieje sposób na przekazanie zmiennych z powrotem do głównego procesu?
Bobby
97

Zobacz równolegle . Jego składnia jest podobna do xargs, ale uruchamia polecenia równolegle.

choroba
źródło
13
Jest to lepsze niż używanie wait, ponieważ zajmuje się rozpoczynaniem nowych zadań jako ukończonych starych, zamiast czekać na zakończenie całej partii przed rozpoczęciem następnej.
chepner
5
Na przykład, jeśli masz listę linków w pliku, możesz to zrobić, cat list_of_links.txt | parallel -j 4 wget {}co spowoduje utrzymanie czterech wgets jednocześnie.
Pan Llama,
5
W mieście jest nowy dzieciak o imieniu pexec, który zastępuje parallel.
slashsbin,
2
Podanie przykładu byłoby bardziej pomocne
jterm
1
parallel --jobs 4 < list_of_commands.sh, gdzie list_of_commands.sh to plik z pojedynczym poleceniem (np. wget LINK1uwaga bez &) w każdym wierszu. Może trzeba to zrobić, CTRL+Za bgpotem pozostawić w tle.
weiji14
71

W rzeczywistości xargs może uruchamiać polecenia równolegle dla Ciebie. Jest do tego specjalna -P max_procsopcja wiersza poleceń. Zobaczyć man xargs.

Vader B
źródło
2
+100 to jest świetne, ponieważ jest wbudowane i bardzo proste w użyciu i można to zrobić w jednej linijce
Clay
Świetny w użyciu dla małych pojemników, ponieważ nie są potrzebne żadne dodatkowe pakiety / zależności!
Marco Roy
1
Zobacz to pytanie, na przykład: stackoverflow.com/questions/28357997/...
Marco Roy
7

Możesz uruchomić 20 procesów i użyć polecenia:

wait

Twój skrypt będzie czekał i będzie kontynuowany po zakończeniu wszystkich zadań w tle.

Binpix
źródło