usando paralela para processar arquivos de entrada exclusivos em arquivos de saída exclusivos

13

Eu tenho um problema de script de shell onde recebo um diretório cheio de arquivos de entrada (cada arquivo contém muitas linhas de entrada), e eu preciso processá-los individualmente, redirecionando cada uma das suas saídas para um arquivo exclusivo (também conhecido como file_1 .input precisa ser capturado em file_1.output e assim por diante).

Pré-paralelo , eu apenas faria uma iteração sobre cada arquivo no diretório e executaria meu comando, enquanto fazia algum tipo de técnica de contagem / temporização para não sobrecarregar os processadores (supondo que cada processo tenha um tempo de execução constante). No entanto, sei que nem sempre será esse o caso, portanto, usar uma solução semelhante a "paralela" parece ser a melhor maneira de obter o multi-threading de script de shell sem escrever código personalizado.

Embora eu tenha pensado em algumas maneiras de trabalhar paralelamente para processar cada um desses arquivos (e permitir que eu gerencie meus núcleos de forma eficiente), todos eles parecem hacky. Eu tenho o que eu acho que é um caso de uso muito fácil, então prefiro mantê-lo o mais limpo possível (e nada nos exemplos paralelos parece saltar 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

Script:

> cat proces_script.sh
#!/bin/sh

customScript -c 33 -I -file [inputFile] -a -v 55 > [outputFile]

Atualizar : Depois de ler a resposta de Ole abaixo, consegui juntar as peças que faltavam para minha própria implementação paralela. Enquanto sua resposta é ótima, aqui estão minhas pesquisas e anotações:

Em vez de executar meu processo completo, resolvi começar com um comando de prova de conceito para provar sua solução em meu ambiente. Veja minhas duas diferentes implementações (e notas):

find /home/me/input_files -type f -name *.txt | parallel cat /home/me/input_files/{} '>' /home/me/output_files/{.}.out

Usa localizar (não ls, que podem causar problemas) para localizar todos os arquivos aplicáveis no diretório de arquivos de entrada e, em seguida, redireciona o conteúdo para um diretório e arquivo separados. Meu problema de cima estava lendo e redirecionando (o script real era simples), então substituir o script pelo gato era uma boa prova de conceito.

parallel cat '>' /home/me/output_files/{.}.out :::  /home/me/input_files/*

Esta segunda solução usa o paradigma de variáveis de entrada do paralelismo para ler os arquivos, no entanto, para um iniciante, isso era muito mais confuso. Para mim, usar o find a e o pipe satisfaz minhas necessidades.

    
por J Jones 20.02.2012 / 04:17

6 respostas

20

O GNU Parallel é projetado para esse tipo de tarefa:

parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output ::: *.input

ou:

ls | parallel customScript -c 33 -I -file {} -a -v 55 '>' {.}.output

Ele executará um trabalho por núcleo de CPU.

Você pode instalar o GNU Parallel simplesmente por:

wget https://git.savannah.gnu.org/cgit/parallel.git/plain/src/parallel
chmod 755 parallel
cp parallel sem

Assista aos vídeos de introdução do GNU Parallel para saber mais: link

    
por 23.02.2012 / 00:22
5

A maneira padrão de fazer isso é configurar uma fila e gerar qualquer número de trabalhadores que saiba 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:

#!/bin/sh
mkfifo /tmp/location-queue
for i in inputfiles/*; do
  echo $i > /tmp/location-queue
done
rm /tmp/location-queue

E um trabalhador:

#!/bin/sh
while read file < /tmp/location-queue; do
  process_file "$file"
done

process_file pode ser definido em algum lugar em seu worker e pode fazer o que for necessário.

Depois de ter essas duas partes, você pode ter um monitor simples que inicia o processo de fila e qualquer número de processos de trabalho.

Script de monitor:

#!/bin/sh
queue.sh &
num_workers="$1"
i=0
while [ $i < $num_workers ]; do
  worker.sh &
  echo $! >> /tmp/worker.pids
  i=$((i+1))
done
monitor_workers

Lá você tem. 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 fiquem presos a um local específico para o fifo. Eu configurei isto desta maneira na resposta especificamente, então está claro que o que você está usando enquanto lê.

    
por 20.02.2012 / 06:27
4

Uma ferramenta comumente disponível que pode fazer paralelização é fazer. O GNU make e alguns outros possuem uma opção -j para realizar compilações paralelas.

.SUFFIXES: .input .output
.input.output:
        process_one_file <$< >[email protected]
        mv -f [email protected] $@

Execute make desta forma (suponho que seus nomes de arquivo não contenham nenhum caractere especial, make não é bom para eles):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)
    
por 21.02.2012 / 03:15
3

Isso é para executar o mesmo comando em um grande conjunto de arquivos no diretório atual:

#!/bin/sh
trap 'worker='expr $worker - 1'' USR1  # free up a worker
worker=0  # current worker
num_workers=10  # maximum number of workers
for file in *.txt; do
    if [ $worker -lt $num_workers ]; then
        {   customScript -c 33 -I -file $file -a -v 55 > 'basename $file .txt'.outtxt 
            kill -USR1 $$ 2>/dev/null  # signal parent that we're free
        } &
        echo $worker/$num_worker $! $file  # feedback to caller
        worker='expr $worker + 1'
    else
        wait # for a worker to finish
    fi
done

Isso executa o customScript em cada arquivo txt , colocando a saída em outtxt files. Mude como você precisa. A chave para que isso funcione é o processamento de sinal, usando SIGUSR1 para que o processo filho possa deixar o processo pai saber que está pronto. O uso do SIGCHLD não funcionará, pois a maioria das instruções do script gerará sinais SIGCHLD para o shell script. Eu tentei isso substituindo seu comando com sleep 1 , o programa usou 0.28s de CPU do usuário e 0.14s do sistema cpu; isso foi apenas em cerca de 400 arquivos.

    
por 20.02.2012 / 16:10
3

Outro exemplo:

ls *.txt | parallel 'sort {} > {.}.sorted.txt'

Eu achei os outros exemplos desnecessariamente complexos, quando na maioria dos casos o que você está procurando é o que você está procurando.

    
por 21.01.2016 / 10:45
0

Ou simplesmente use xargs -P , não é necessário instalar software adicional:

find . -type f -print0 | xargs -0 -I'XXX' -P4 -n1 custom_script -input "XXX" -output "XXX.out"

Um pouco de explicação para as opções:

  • -I'XXX' define a cadeia que será substituída no modelo de comando com o nome do arquivo
  • -P4 executará 4 processos em paralelo
  • -n1 colocará apenas um arquivo por execução, mesmo que dois XXX sejam encontrados
  • -print0 e -0 trabalham juntos, permitindo que você tenha caracteres especiais (como espaço em branco) nos nomes dos arquivos
por 05.08.2018 / 19:29