Bash-скрипт обрабатывает ограниченное количество команд параллельно

196

У меня есть скрипт bash, который выглядит так:

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

Но обработка каждой строки до тех пор, пока команда не завершится, а переход к следующей занимает очень много времени, я хочу обработать, например, 20 строк одновременно, а затем, когда они завершатся, будут обработаны еще 20 строк.

Я думал о том, wget LINK1 >/dev/null 2>&1 &чтобы отправить команду в фоновый режим и продолжить, но здесь 4000 строк, это означает, что у меня будут проблемы с производительностью, не говоря уже об ограничении количества процессов, которые я должен запускать одновременно, так что это не очень хорошо идея.

Одним из решений, о котором я сейчас думаю, является проверка, выполняется ли еще одна из команд или нет, например, после 20 строк я могу добавить этот цикл:

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

Конечно, в этом случае мне нужно будет добавить & в конец строки! Но я чувствую, что это не правильный способ сделать это.

Итак, как мне на самом деле сгруппировать каждые 20 строк и ждать, пока они закончат, прежде чем перейти к следующим 20 строкам, этот сценарий генерируется динамически, поэтому я могу делать с ним все, что захочу, пока он генерируется, но он НЕ ДОЛЖЕН используйте wget, это был просто пример, так что любое решение, специфичное для wget, не принесет мне пользы.

AL-Катеб
источник
1
waitздесь правильный ответ, но ваш while [ $(ps …будет гораздо лучше написан while pkill -0 $KEYWORD…- используя proctools … то есть, по уважительным причинам, чтобы проверить, все еще ли запущен процесс с определенным именем.
Кодзиро
Я думаю, что этот вопрос должен быть вновь открыт. QA «возможный дубликат» - это параллельное выполнение конечного числа программ. Вроде 2-3 команды. Этот вопрос, однако, сосредоточен на выполнении команд, например, в цикле. (см. «но есть 4000 строк»).
Василий Новиков
@VasyaNovikov Вы прочитали все ответы как на этот вопрос, так и на дубликаты? Каждый отдельный ответ на этот вопрос здесь также можно найти в ответах на дублирующий вопрос. То есть именно определение дубликата вопроса. Не имеет значения, выполняете ли вы команды в цикле или нет.
robinCTS
@robinCTS есть пересечения, но сами вопросы разные. Кроме того, 6 из самых популярных ответов на связанный QA касаются только 2 процессов.
Василий Новиков
2
Я рекомендую вновь открыть этот вопрос, потому что его ответ яснее, чище, лучше и гораздо более высоко оценен, чем ответ на связанный вопрос, хотя это на три года позже.
Дан Ниссенбаум

Ответы:

331

Используйте waitвстроенные:

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

В приведенном выше примере 4 процесса process1... process4будут запущены в фоновом режиме, и оболочка будет ожидать завершения этих процессов перед запуском следующего набора.

Из руководства GNU :

wait [jobspec or pid ...]

Дождитесь завершения дочернего процесса, заданного каждым идентификатором процесса pid или спецификацией задания, и верните состояние завершения последней ожидаемой команды. Если задана спецификация задания, все процессы в задании ожидаются. Если аргументы не указаны, ожидаются все активные в данный момент дочерние процессы, а статус возврата равен нулю. Если ни jobspec, ни pid не указывают активный дочерний процесс оболочки, возвращаемое состояние - 127.

devnull
источник
14
Так что в основномi=0; waitevery=4; for link in "${links[@]}"; do wget "$link" & (( i++%waitevery==0 )) && wait; done >/dev/null 2>&1
Кодзиро
18
Если вы не уверены, что каждый процесс завершится в одно и то же время, это плохая идея. Вам нужно запускать новые задания, чтобы поддерживать текущее общее количество заданий на определенном уровне .... параллель - это ответ.
Rushaw
1
Есть ли способ сделать это в цикле?
ДоменыРекомендуемые
Я пробовал это, но кажется, что назначения переменных, сделанные в одном блоке, не доступны в следующем блоке. Это потому, что они являются отдельными процессами? Есть ли способ передать переменные обратно в основной процесс?
Бобби
97

Смотрите параллельно . Его синтаксис похож на xargs, но он выполняет команды параллельно.

choroba
источник
13
Это лучше, чем использовать wait, так как он заботится о запуске новых заданий как завершенных старых, а не о ожидании завершения всего пакета перед началом следующего.
chepner
5
Например, если у вас есть список ссылок в файле, вы можете сделать так cat list_of_links.txt | parallel -j 4 wget {}, чтобы одновременно wgetработали четыре файла .
Мистер Лама
5
В городе появился новый ребенок по имени pexec, который является заменой parallel.
slashsbin
2
Предоставление примера было бы более полезным
jterm
1
parallel --jobs 4 < list_of_commands.shгде list_of_commands.sh - файл с одной командой (например wget LINK1, примечание без &) в каждой строке. Возможно, потребуется сделать CTRL+Zи bgпосле этого оставить его работающим в фоновом режиме.
weiji14
71

На самом деле, xargs может запускать команды параллельно для вас. Для этого есть специальный параметр -P max_procsкомандной строки. См man xargs.

Вейдер Б
источник
2
+100 это здорово, так как он встроен и очень прост в использовании и может быть выполнен в одну
Clay
Отлично подходит для небольших контейнеров, так как никаких дополнительных пакетов / зависимостей не требуется!
Марко Рой
1
См. Этот вопрос для примеров: stackoverflow.com/questions/28357997/…
Марко Рой
7

Вы можете запустить 20 процессов и использовать команду:

wait

Ваш сценарий будет ждать и продолжать, когда все ваши фоновые задания будут завершены.

Binpix
источник