GNU parallel: Executando Jobs em Paralelo pelo Shell
O Que Você Vai Aprender
- Executar muitos comandos simultaneamente entre núcleos de CPU para reduzir o tempo total
- Evitar a saída intercalada e fora de ordem que você obtém com
xargs -P - Usar
--jobloge--resumepara retomar de onde parou, pulando jobs concluídos
Resumo Rápido
- Muitas tarefas pesadas e independentes (converter, baixar, testar) ->
parallel - Precisa de saída limpa / ordem de entrada preservada ->
-k - Quer retomar uma execução interrompida ->
--joblog+--resume
Premissas (ambiente alvo)
- Ubuntu / família Debian (os conceitos se aplicam a outras distros também)
- Este é o GNU parallel (por Ole Tange). O
paralleldiferente fornecido emmoreutilsnão é compatível
O que é GNU parallel?
Conclusão: Ele recebe uma lista (de stdin ou argumentos de linha de comando) e executa um comando em cada item em paralelo. Por padrão, executa um job por núcleo de CPU.
GNU parallel pega a ideia do xargs de "construir um comando a partir de uma lista" e a especializa para execução paralela e saída limpa. Duas formas básicas:
# 1) argumentos apos ::: parallel echo ::: a b c # 2) da entrada padrao seq 1 3 | parallel echo
a b c
Cada um de a, b, c lanca echo como um processo separado ao mesmo tempo. A concorrência padrão é a quantidade de núcleos de CPU, então mais entradas que núcleos são alimentadas nos slots conforme eles ficam livres.
Por padrão, um comando é executado por item de entrada (o oposto do xargs). Para empacotar vários argumentos em uma invocação, use -N (coberto abaixo).
Como instalar e executar o primeiro comando?
Conclusão: Instale com
apt install parallel. No primeiro uso, ele imprime um aviso de citação; executeparallel --citationuma vez para registrar seu reconhecimento.
sudo apt update sudo apt install parallel parallel --version
GNU parallel pede para ser citado em trabalhos acadêmicos e imprime uma solicitação de citação no primeiro uso. Se isso atrapalha scripts ou CI, execute o seguinte uma vez para registrar o reconhecimento (cria ~/.parallel/will-cite e silencia o aviso).
parallel --citation
Em algumas distros o pacote moreutils fornece um parallel diferente. Sempre verifique se a primeira linha de parallel --version diz GNU parallel. Se não disser, não é a ferramenta GNU.
Por que usar parallel em vez de xargs?
Conclusão:
xargs -Ptambém pode executar em paralelo, mas sua saída intercala linha por linha. parallel agrupa a saída por job e pode manter a ordem de entrada com-k.
xargs -P 4 é prático, mas a saída padrão de jobs concorrentes tende a intercalar uma linha por vez. parallel armazena a saída de cada job internamente e a emite como um todo quando o job termina, então nunca mistura.
| Aspecto | xargs -P |
parallel |
|---|---|---|
| Intercalação de saída | Provável | Agrupada por job |
| Saída na ordem de entrada | Não garantida | Garantida com -k |
| Poder de placeholders | Apenas {} |
{} {.} {/} etc. |
| Log de execução / resume | Nenhum | --joblog --resume |
| Exibição de progresso | Nenhuma | --bar --eta |
Para velocidade bruta sozinha, xargs -P geralmente é suficiente. parallel ganha seu espaço quando você não pode corromper a saída ou quer retomar.
Como os placeholders funcionam?
Conclusão:
{}é a própria entrada.{.}remove a extensão,{/}é o nome base,{//}o diretório,{#}o número do job. Eles são essenciais para construir nomes de saída.
Placeholders dizem ao parallel onde inserir cada entrada. Se você os omitir, um {} é adicionado ao final.
# Converter cada *.wav para um .mp3 de mesmo nome ({.} remove a extensao)
parallel ffmpeg -i {} {.}.mp3 ::: *.wavOs principais placeholders:
| Sintaxe | Significado | Exemplo (entrada dir/file.txt) |
|---|---|---|
{} |
A própria entrada | dir/file.txt |
{.} |
Extensão removida | dir/file |
{/} |
Nome base (diretório removido) | file.txt |
{//} |
Parte do diretório | dir |
{/.} |
Nome base sem extensão | file |
{#} |
Número sequencial do job | 1, 2, ... |
{%} |
Número do slot do job | 1..(até a concorrência) |
# Prefixar cada entrada com seu numero de job
parallel 'echo job {#}: {}' ::: alpha beta gammajob 1: alpha job 2: beta job 3: gamma
Como controlar a quantidade de jobs e a ordem da saída?
Conclusão: Defina a concorrência com
-j.-j0executa o máximo possível,-j 200%é o dobro da quantidade de núcleos. Adicione-kpara emitir saída na ordem de entrada.
# 4 jobs por vez
parallel -j 4 ./convert.sh ::: *.dat
# Dobro da quantidade de nucleos (bom para trabalho limitado por I/O)
parallel -j 200% curl -O ::: "${urls[@]}"
# Sem limite de concorrencia (use com cuidado)
parallel -j0 echo ::: {1..100}Com execução paralela, a saída chega na ordem de conclusão, quebrando o mapeamento entrada-saída. Para emitir na ordem de entrada, adicione -k (--keep-order).
seq 1 5 | parallel -k 'sleep $((RANDOM % 3)); echo {}'1 2 3 4 5
Antes de ir para produção, adicione --dry-run para imprimir apenas as linhas de comando que o parallel executaria. Detecte erros de expansão de placeholder aqui.
Como combinar múltiplas entradas?
Conclusão: Múltiplos
:::produzem o produto cartesiano.--linkemparelha itens por posição. Para linhas multi-coluna, use--colsepe referencie{1}{2}.
# Produto cartesiano: a-1 a-2 b-1 b-2 c-1 c-2 (6 jobs) parallel echo ::: a b c ::: 1 2
# --link: emparelhar por posicao -> a-1 b-2 c-3 parallel --link echo ::: a b c ::: 1 2 3
Para ler entradas de um arquivo, use :::: (ou -a). Para dividir colunas como um CSV, use --colsep.
# Usar cada linha de hosts.txt como argumento
parallel ping -c1 {} :::: hosts.txt
# Dividir "user,host" em colunas
parallel --colsep ',' ssh {2} -l {1} uptime :::: targets.csvComo mostrar progresso, log e reexecutar falhas?
Conclusão:
--barmostra uma barra de progresso,--joblogregistra o resultado de cada job. Adicione--resumepara levar apenas os jobs não concluídos para a próxima execução.
# Mostrar uma barra de progresso
parallel --bar ./task.sh ::: {1..50}
# Registrar um log de execucao (codigo de saida e duracao por job)
parallel --joblog run.log ./task.sh ::: *.datCom um --joblog registrado, --resume pula jobs que já tiveram sucesso e continua. Para tentar novamente apenas os que falharam, use --resume-failed.
# Apos uma interrupcao/falha, adicione --resume ao mesmo comando parallel --joblog run.log --resume ./task.sh ::: *.dat
Para parar cedo em um erro, use --halt.
# Parar apos uma falha, deixando jobs em execucao terminarem parallel --halt now,fail=1 ./task.sh ::: *.dat
--resume assume o mesmo arquivo --joblog e o mesmo comando. Mudar o comando ou as entradas quebra a retomada correta.
O que é o modo --pipe para dividir stdin?
Conclusão:
--pipedivide o próprio fluxo de entrada padrão em blocos e alimenta cada bloco para um comando paralelo. É adequado para agregar logs enormes.
Até agora paralelizamos uma lista de argumentos. --pipe em vez disso divide um único fluxo de entrada para processamento paralelo.
# Dividir um arquivo enorme em blocos de 10MB e grep cada um em paralelo cat huge.log | parallel --pipe --block 10M grep ERROR
--block define o tamanho de cada bloco. parallel divide nos limites de quebra de linha para que linhas nunca sejam cortadas ao meio.
Receitas práticas
Conclusão: Conversão em massa de imagens, downloads em massa e execução de um comando em muitos hosts são os clássicos. Confirme com
--dry-runantes de ir para produção.
Templates para copiar e colar
# Redimensionar imagens em massa (saida nomeada {.}_small.jpg)
parallel convert {} -resize 50% {.}_small.jpg ::: *.jpg
# Baixar uma lista de URLs com 8 jobs
parallel -j8 wget -q ::: $(cat urls.txt)
# Mesmo comando em muitos hosts (saida agrupada por host)
parallel -k --tag ssh {} 'uptime' :::: hosts.txt
# Visualizar primeiro; remova --dry-run quando parecer correto
parallel --dry-run ./batch.sh {} ::: input/*Adicionar --tag prefixa cada linha de saída com sua entrada (como o nome do host), facilitando identificar qual job produziu qual resultado.