web-dev-qa-db-fra.com

en utilisant parallèlement pour traiter des fichiers d'entrée uniques à des fichiers de sortie uniques

J'ai un problème de script shell où j'ai reçu un répertoire rempli de fichiers d'entrée (chaque fichier contenant de nombreuses lignes d'entrée), et j'ai besoin de les traiter individuellement, redirection de chacune de leurs sorties vers un fichier unique (aka, fichier_1.Input être capturé dans fichier_1.output, etc.).

pré-parallèle, je voudrais simplement itérer sur chaque fichier dans le répertoire et effectuer ma commande, tout en faisant une sorte de technique de minuterie/comptant pour ne pas submerger les processeurs ( en supposant que chaque processus ait un runtime constant). Cependant, je sais que ce n'est pas toujours le cas, il ne sert donc pas à utiliser une solution "parallèle" comme la meilleure façon d'obtenir un script shell multi-threading sans écrire le code personnalisé.

Bien que j'ai pensé à certaines façons de préparer parallèlement à traiter chacun de ces fichiers (et de me permettre de gérer mes noyaux efficacement), ils semblent tous hacky. J'ai ce que je pense, c'est un cas d'utilisation assez facile, vous préféreriez donc le garder aussi propre que possible (et rien dans les exemples parallèles ne semblerait sauter comme étant mon problème.

Toute aide serait appréciée!

exemple de répertoire d'entrée:

> ls -l input_files/
total 13355
location1.txt
location2.txt
location3.txt
location4.txt
location5.txt

Scénario:

> cat proces_script.sh
#!/bin/sh

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

Mise à jour: Après avoir lu la réponse de OLE ci-dessous, j'ai pu réunir les pièces manquantes pour ma propre implémentation parallèle. Bien que sa réponse soit excellente, voici mes additions de recherche et de note que j'ai prises:

Au lieu de courir mon processus complet, j'ai pensé à commencer par une preuve de commandement de concept pour prouver sa solution dans mon environnement. Voir mes deux différentes implémentations (et mes notes):

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

Utilisations Recherche (non LS, qui peut causer des problèmes) pour trouver tous les fichiers applicables de mon répertoire de fichiers d'entrée, puis redirige leur contenu dans un répertoire et un fichier distincts. Mon problème de ci-dessus lisait et redirigeait (le script actuel était simple), il est donc de remplacer le script avec CAT était une faible preuve de concept.

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

Cette seconde solution utilise un paradigme variable d'entrée de parallèle pour lire les fichiers. Toutefois, pour un novice, c'était beaucoup plus déroutant. Pour moi, en utilisant Trouver A et Tuyau répond à mes besoins.

18
J Jones

GNU Parallel est conçu pour ce type de tâches:

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

ou:

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

Il dirigera un travail par cè cépu.

Vous pouvez installer GNU parallèle simplement par:

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

Regardez les vidéos intro pour GNU parallèle pour en savoir plus: https://www.youtube.com/joulist?list=pl284C9FF2488BC6D1

27
Ole Tange

Le moyen standard de le faire est de configurer une file d'attente et de reproduire tout nombre de travailleurs qui savent tirer quelque chose de la file d'attente et de le traiter. Vous pouvez utiliser une FIFO (aka Tuyau nommé) pour la communication entre ces processus.

Vous trouverez ci-dessous un exemple naïf pour démontrer le concept.

Un simple script de file d'attente:

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

Et un travailleur:

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

process_file Pourrait être défini quelque part chez votre travailleur, et cela peut faire tout ce que vous en avez besoin pour faire.

Une fois que vous avez ces deux pièces, vous pouvez avoir un moniteur simple qui démarre le processus de file d'attente et de tout nombre de processus de travail.

Script de surveillance:

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

Voilà. Si vous le faites réellement, il est préférable de configurer la FIFO dans le moniteur et de passer le chemin de la file d'attente et des travailleurs, de sorte qu'ils ne sont pas couplés et non bloqués à un endroit spécifique pour la FIFO. Je l'ai mis de cette façon dans la réponse spécifiquement, il est donc clair que ce que vous utilisez au fur et à mesure que vous le lisez.

5
Shawn J. Goff

Un autre exemple:

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

J'ai trouvé les autres exemples inutilement complexes, lorsque dans la plupart des cas, ce qui précède est ce que vous avez peut-être cherché.

5
deceleratedcaviar

Un outil communément disponible pouvant faire la parallélisation est de faire. GNU faire et quelques autres ont un -j Option pour effectuer des constructions parallèles.

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

Run make Comme ceci (je suppose que vos noms de fichier ne contiennent aucun caractère spécial, make n'est pas bon avec ceux):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)

Ceci est pour exécuter une même commande sur un ensemble important de fichiers dans le répertoire actuel:

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

Cela exécute le fichier customScript sur chaque fichier txt, mettez la sortie dans outtxt fichiers. Changez comme vous avez besoin. La clé pour que cela fonctionne pour que cela fonctionne est le traitement du signal, à l'aide de SIGUSR1 afin que le processus d'enfant puisse laisser le processus parent savoir qu'elle est terminée. L'utilisation de SIGCHLD ne fonctionnera pas car la plupart des instructions du script généreront des signaux SIGCHLD au script Shell. J'ai essayé cela en remplaçant votre commande avec sleep 1, le programme utilisé 0,28 de CPU utilisateur et 0,14s de processeur système; C'était seulement sur environ 400 fichiers.

3
Arcege

Ou simplement utiliser xargs -P, pas besoin d'installer un logiciel additionnel:

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

Un peu d'explication pour les options:

  • -I'XXX' Définit la chaîne qui sera remplacée dans le modèle de commande avec le nom de fichier
  • -P4 exécutera 4 processus en parallèle
  • -n1 ne mettra qu'un seul fichier par exécution même si deux xxx sont trouvés
  • -print0 et -0 Travaillez ensemble, vous permettant d'avoir des caractères spéciaux (comme WhitSpace) dans les noms de fichiers
0
Piotr Czapla