Processamento de script bash número limitado de comandos em paralelo

196

Eu tenho um script bash que se parece com isso:

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

Mas processar cada linha até que o comando seja concluído e passar para a próxima consome muito tempo. Quero processar, por exemplo, 20 linhas de uma só vez e, quando terminar, outras 20 linhas serão processadas.

Pensei wget LINK1 >/dev/null 2>&1 &em enviar o comando para segundo plano e continuar, mas existem 4000 linhas aqui, isso significa que terei problemas de desempenho, sem mencionar que estou limitado em quantos processos devo iniciar ao mesmo tempo, para que isso não seja uma boa ideia. idéia.

Uma solução em que estou pensando agora é verificar se um dos comandos ainda está em execução ou não, por exemplo, após 20 linhas, posso adicionar este loop:

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

É claro que, neste caso, precisarei anexar e ao final da linha! Mas sinto que esse não é o caminho certo para fazê-lo.

Então, como eu realmente agrupo cada 20 linhas e espero que elas terminem antes de passar para as próximas 20 linhas, esse script é gerado dinamicamente para que eu possa fazer qualquer matemática que eu quiser enquanto estiver sendo gerado, mas NÃO PRECISA use wget, era apenas um exemplo, então qualquer solução específica do wget não me ajudaria.

AL-Kateb
fonte
1
waité a resposta certa aqui, mas você while [ $(ps …seria muito melhor escrito while pkill -0 $KEYWORD…- usando proctools ... ou seja, por razões legítimas para verificar se um processo com um nome específico ainda está em execução.
Kojiro # 23/13
Penso que esta questão deve ser reaberta. O controle de qualidade "possível duplicata" é sobre a execução de um número finito de programas em paralelo. Como 2-3 comandos. Esta questão, no entanto, está focada na execução de comandos, por exemplo, em um loop. (consulte "mas existem 4000 linhas").
VasiliNovikov 11/01/19
@VasyaNovikov Você leu todas as respostas para esta pergunta e para a duplicata? Cada resposta única a esta pergunta aqui também pode ser encontrada nas respostas à pergunta duplicada. Essa é precisamente a definição de uma pergunta duplicada. Não faz absolutamente nenhuma diferença se você está executando ou não os comandos em um loop.
precisa saber é o seguinte
@robinCTS existem interseções, mas as perguntas são diferentes. Além disso, seis das respostas mais populares no controle de qualidade vinculado tratam apenas de dois processos.
VasiliNovikov
2
Eu recomendo reabrir esta pergunta porque sua resposta é mais clara, mais limpa, melhor e muito mais votada do que a resposta da pergunta vinculada, embora seja três anos mais recente.
Dan Nissenbaum

Respostas:

331

Use o waitbuilt-in:

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

Para o exemplo acima, 4 processos process1... process4seriam iniciados em segundo plano e o shell aguardaria até que eles fossem concluídos antes de iniciar o próximo conjunto.

No manual GNU :

wait [jobspec or pid ...]

Aguarde até que o processo filho especificado por cada ID de processo pid ou especificação de tarefa jobspec saia e retorne o status de saída do último comando esperado. Se uma especificação de trabalho for fornecida, todos os processos no trabalho serão aguardados. Se nenhum argumento for fornecido, todos os processos filhos ativos no momento serão aguardados e o status de retorno será zero. Se nem jobspec nem pid especificarem um processo filho ativo do shell, o status de retorno será 127.

devnull
fonte
14
Então, basicamentei=0; waitevery=4; for link in "${links[@]}"; do wget "$link" & (( i++%waitevery==0 )) && wait; done >/dev/null 2>&1
kojiro
18
A menos que você tenha certeza de que cada processo será concluído exatamente ao mesmo tempo, é uma má idéia. Você precisa iniciar novos trabalhos para manter o total atual de trabalhos em um determinado limite .... paralelo é a resposta.
rsaw
1
Existe uma maneira de fazer isso em um loop?
DomainsFeatured
Eu tentei isso, mas parece que as atribuições de variáveis ​​feitas em um bloco não estão disponíveis no próximo bloco. Isso é porque eles são processos separados? Existe uma maneira de comunicar as variáveis ​​de volta ao processo principal?
Bobby
97

Veja paralelo . Sua sintaxe é semelhante a xargs, mas executa os comandos em paralelo.

choroba
fonte
13
É melhor do que usar wait, pois cuida de iniciar novos trabalhos à medida que os antigos são concluídos, em vez de aguardar a conclusão de um lote inteiro antes de iniciar o próximo.
chepner
5
Por exemplo, se você tiver a lista de links em um arquivo, poderá fazer o cat list_of_links.txt | parallel -j 4 wget {}que manterá quatro wgetsegundos em execução por vez.
Llama
5
Há um novo garoto na cidade chamado pexec, que é um substituto parallel.
slashsbin
2
Dar um exemplo seria mais útil
jterm 11/01/19
1
parallel --jobs 4 < list_of_commands.sh, em que list_of_commands.sh é um arquivo com um único comando (por exemplo wget LINK1, observe sem o &) em todas as linhas. Pode ser necessário fazer CTRL+Ze bgdepois deixá-lo em execução em segundo plano.
weiji14
71

De fato, xargs pode executar comandos em paralelo para você. Existe uma -P max_procsopção de linha de comando especial para isso. Veja man xargs.

Vader B
fonte
2
+100 este é é grande, uma vez que é construído em e muito simples de usar e pode ser feito em um one-liner
argila
Ótimo para usar em contêineres pequenos, pois não são necessários pacotes / dependências extras!
Marco Roy
1
Veja esta pergunta para exemplos: stackoverflow.com/questions/28357997/…
Marco Roy
7

Você pode executar 20 processos e usar o comando:

wait

Seu script aguardará e continuará quando todos os seus trabalhos em segundo plano forem concluídos.

Binpix
fonte