Eu tenho um problema de script de shell no qual recebo um diretório cheio de arquivos de entrada (cada arquivo contém muitas linhas de entrada) e preciso processá-los individualmente, redirecionando cada uma de suas saídas para um arquivo exclusivo (também conhecido como file_1.input para ser capturado em file_1.output e assim por diante).
Antes do paralelo , eu simplesmente iterava sobre cada arquivo no diretório e executava meu comando, enquanto fazia algum tipo de técnica de contagem / timer para não sobrecarregar os processadores (assumindo que cada processo tivesse um tempo de execução constante). No entanto, eu sei que nem sempre será o caso, portanto, usar uma solução "paralela" parece a melhor maneira de obter multi-threading de script de shell sem escrever código personalizado.
Embora tenha pensado em algumas maneiras de criar paralelos para processar cada um desses arquivos (e me permitir gerenciar meus núcleos com eficiência), todos eles parecem hacky. Eu tenho o que eu acho que é um caso de uso bastante fácil, por isso preferiria mantê-lo o mais limpo possível (e nada nos exemplos paralelos parece pular como sendo o meu problema.
Qualquer ajuda seria apreciada!
exemplo de diretório de entrada:
> ls -l input_files/
total 13355
location1.txt
location2.txt
location3.txt
location4.txt
location5.txt
Roteiro:
> cat proces_script.sh
#!/bin/sh
customScript -c 33 -I -file [inputFile] -a -v 55 > [outputFile]
Atualização : Depois de ler a resposta de Ole abaixo, pude reunir as peças que faltavam para minha própria implementação paralela. Embora sua resposta seja ótima, aqui está minha pesquisa adicional e as anotações que fiz:
Em vez de executar todo o meu processo, imaginei começar com um comando de prova de conceito para provar sua solução no meu ambiente. Veja minhas duas implementações diferentes (e notas):
find /home/me/input_files -type f -name *.txt | parallel cat /home/me/input_files/{} '>' /home/me/output_files/{.}.out
Utiliza find (não ls, que pode causar problemas) para encontrar todos os arquivos aplicáveis no diretório de arquivos de entrada e, em seguida, redireciona seu conteúdo para um diretório e arquivo separados. Meu problema acima foi a leitura e o redirecionamento (o script real era simples), portanto, substituir o script por cat foi uma boa prova de conceito.
parallel cat '>' /home/me/output_files/{.}.out ::: /home/me/input_files/*
Esta segunda solução usa o paradigma da variável de entrada paralela para ler os arquivos; no entanto, para um iniciante, isso era muito mais confuso. Para mim, usar find a and pipe atendeu minhas necessidades.
fonte
A maneira padrão de fazer isso é configurar uma fila e gerar qualquer número de trabalhadores que sabem como extrair algo da fila e processá-lo. Você pode usar um fifo (também conhecido como pipe nomeado) para comunicação entre esses processos.
Abaixo está um exemplo ingênuo para demonstrar o conceito.
Um script de fila simples:
E um trabalhador:
process_file
pode ser definido em algum lugar do seu trabalhador e pode fazer o que você precisar.Depois de ter essas duas partes, é possível ter um monitor simples que inicie o processo da fila e qualquer número de processos de trabalho.
Script de monitor:
Aí está. Se você realmente fizer isso, é melhor configurar o fifo no monitor e passar o caminho para a fila e os trabalhadores, para que eles não sejam acoplados e não estejam presos a um local específico para o fifo. Eu o configurei dessa maneira na resposta especificamente, para que fique claro o que você está usando ao lê-lo.
fonte
monitor_workers
é comoprocess_file
- é uma função que faz o que você quiser. Sobre o monitor - você estava certo; ele deve salvar os pids de seus trabalhadores (para que ele possa enviar um sinal de interrupção) e o contador precisa ser incrementado quando ele inicia um trabalhador. Eu editei a resposta para incluir isso.parallel
. Eu acho que é sua ideia, totalmente implementada.Outro exemplo:
Achei os outros exemplos desnecessariamente complexos, quando, na maioria dos casos, é o que você está procurando acima.
fonte
Uma ferramenta comumente disponível que pode fazer paralelização é o make. O GNU make e alguns outros têm uma
-j
opção para executar compilações paralelas.Execute
make
assim (presumo que os nomes dos seus arquivos não contenham caracteres especiais,make
não é bom com eles):fonte
Isso é para executar o mesmo comando em um grande conjunto de arquivos no diretório atual:
Isso executa o arquivo
customScript
em cadatxt
arquivo, colocando a saída emouttxt
arquivos. Mude conforme necessário. A chave para fazer isso funcionar é o processamento do sinal, usando SIGUSR1, para que o processo filho possa informar ao processo pai que está pronto. O uso do SIGCHLD não funcionará, pois a maioria das instruções no script gerará sinais do SIGCHLD para o script do shell. Eu tentei isso substituindo seu comando porsleep 1
, o programa usava 0,28s de CPU do usuário e 0,14s de CPU do sistema; isso foi apenas em cerca de 400 arquivos.fonte
wait
que é 'inteligente' o suficiente; mas ele retornará após receber oSIGUSR1
sinal. O filho / trabalhador envia umSIGUSR1
para o pai, que é capturado (trap
) e diminui$worker
(trap
cláusula) e retorna anormalmente dewait
, permitindo que aif [ $worker -lt $num_workers ]
cláusula seja executada.Ou simplesmente use
xargs -P
, não é necessário instalar software adicional:Um pouco de explicação para as opções:
-I'XXX'
define a sequência que será substituída no modelo de comando pelo nome do arquivo-P4
irá executar 4 processos em paralelo-n1
colocará apenas um arquivo por execução, mesmo que dois XXX sejam encontrados-print0
e-0
trabalhar juntos, permitindo que você tenha caracteres especiais (como espaço em branco) nos nomes dos arquivosfonte