Verwenden von Parallel, um eindeutige Eingabedateien in eindeutige Ausgabedateien zu verarbeiten

18

Ich habe ein Shell-Scripting-Problem, bei dem ich ein Verzeichnis voller Eingabedateien (jede Datei enthält viele Eingabezeilen) bekomme, und ich muss sie einzeln verarbeiten und jede ihrer Ausgaben in eine eindeutige Datei umleiten (auch bekannt als file_1.input-Anforderungen) in file_1.output zu erfassen, und so weiter).

Vor der Parallelisierung durchlieferte ich einfach jede Datei im Verzeichnis und führte meinen Befehl aus, während ich eine Art Timer- / Zähltechnik ausführte, um die Prozessoren nicht zu überfordern (vorausgesetzt, jeder Prozess hatte eine konstante Laufzeit). Ich weiß jedoch, dass dies nicht immer der Fall sein wird. Daher scheint die Verwendung einer "parallelen" Lösung der beste Weg zu sein, um das Multithreading von Shell-Skripten zu erreichen, ohne benutzerdefinierten Code zu schreiben.

Obwohl ich mir überlegt habe, wie ich jede dieser Dateien parallel verarbeiten kann (und meine Kerne effizient verwalten kann), scheinen sie alle hacky zu sein. Ich halte das für einen ziemlich einfachen Anwendungsfall und würde es daher vorziehen, es so sauber wie möglich zu halten (und nichts in den parallelen Beispielen scheint als mein Problem herauszuspringen.

Jede Hilfe wäre dankbar!

Beispiel für ein Eingabeverzeichnis:

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

Skript:

> cat proces_script.sh
#!/bin/sh

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

Update : Nachdem ich Ole's Antwort unten gelesen hatte, konnte ich die fehlenden Teile für meine eigene parallele Implementierung zusammenstellen. Während seine Antwort großartig ist, sind hier meine zusätzlichen Nachforschungen und Notizen, die ich gemacht habe:

Anstatt meinen gesamten Prozess auszuführen, begann ich mit einem Proof-of-Concept-Befehl, um seine Lösung in meiner Umgebung zu beweisen. Siehe meine zwei verschiedenen Implementierungen (und Hinweise):

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

Verwendet find (nicht ls, das kann Probleme verursachen), um alle zutreffenden Dateien in meinem Eingabedateiverzeichnis zu finden und leitet ihren Inhalt dann in ein separates Verzeichnis und eine separate Datei um. Mein Problem von oben war das Lesen und Umleiten (das eigentliche Skript war einfach), daher war das Ersetzen des Skripts durch cat ein guter Proof of Concept.

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

Diese zweite Lösung verwendet das Eingabevariablen-Paradigma von parallel, um die Dateien einzulesen. Für Anfänger war dies jedoch viel verwirrender. Mit find a and pipe habe ich meine Bedürfnisse bestens erfüllt.

J Jones
quelle

Antworten:

27

GNU Parallel wurde für diese Art von Aufgaben entwickelt:

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

oder:

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

Es wird ein Job pro CPU-Kern ausgeführt.

Sie können GNU Parallel einfach installieren, indem Sie:

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

In den Introvideos zu GNU Parallel erfahren Sie mehr: https://www.youtube.com/playlist?list=PL284C9FF2488BC6D1

Ole Tange
quelle
Tolle Antwort (und wichtige Punkte zum Lesen meiner Bitte, Parallel zu verwenden).
J Jones
5

Die Standardmethode besteht darin, eine Warteschlange einzurichten und eine beliebige Anzahl von Mitarbeitern zu erzeugen, die wissen, wie sie etwas aus der Warteschlange ziehen und verarbeiten können. Sie können ein FIFO (auch als Named Pipe bezeichnet) für die Kommunikation zwischen diesen Prozessen verwenden.

Nachfolgend finden Sie ein naives Beispiel zur Veranschaulichung des Konzepts.

Ein einfaches Warteschlangenskript:

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

Und ein Arbeiter:

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

process_file könnte irgendwo in Ihrem Arbeiter definiert sein und es kann tun, was immer Sie es tun müssen.

Sobald Sie diese beiden Teile haben, können Sie über einen einfachen Monitor verfügen, der den Warteschlangenprozess und eine beliebige Anzahl von Arbeitsprozessen startet.

Überwachen Sie Skript:

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

Hier hast du es. Wenn Sie dies tatsächlich tun, ist es besser, das FIFO im Monitor einzurichten und den Pfad sowohl an die Warteschlange als auch an die Worker zu übergeben, damit sie nicht gekoppelt sind und nicht an einen bestimmten Ort für das FIFO gebunden sind. Ich habe es in der Antwort so eingestellt, damit klar ist, was Sie verwenden, während Sie es lesen.

Shawn J. Goff
quelle
Wie ist der Monitor intelligent genug, um das Laichen neuer Arbeiter zu unterbrechen, bis das nächste beendet ist (auch bekannt als: Wo werden die US-Dollar jemals dekrementiert?)? ---- Wenn ich meine eigene Bearbeitung beantworte, gehen die Arbeiter nie weg, sie verarbeiten nur Dateien, bis die gesamte Verarbeitung erschöpft ist (daher auch die while-Schleife innerhalb der 'Prozessoren').
J Jones
Was bedeutet die Zeile "monitor_workers" am Ende des Monitor-Skripts?
J Jones
@JJones - monitor_workersist wie process_file- es ist eine Funktion, die macht, was Sie wollen. Über den Monitor - Sie hatten Recht; es sollte die Pids seiner Arbeiter speichern (damit es ein Kill-Signal senden kann) und der Zähler muss erhöht werden, wenn es einen Arbeiter startet. Ich habe die Antwort so bearbeitet, dass sie diese enthält.
Shawn J. Goff
Ich schätze Ihre Arbeit sehr, aber ich denke, Sie sollten GNUs verwenden parallel. Ich denke, es ist Ihre Idee, vollständig umgesetzt.
Motobói
5

Ein anderes Beispiel:

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

Ich fand die anderen Beispiele unnötig komplex, obwohl Sie in den meisten Fällen nach den obigen Beispielen gesucht haben.

gebremster Kaviar
quelle
4

Ein allgemein verfügbares Werkzeug, das Parallelisierung durchführen kann, ist make. GNU make und einige andere haben die -jOption, parallele Builds durchzuführen.

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

Laufen Sie makeso (ich nehme an, dass Ihre Dateinamen keine Sonderzeichen enthalten, makeist nicht gut mit denen):

make -j 4 $(for x in *.input; do echo ${x%.*}.output; done)
Gilles 'SO - hör auf böse zu sein'
quelle
imho das ist die klügste Lösung :)
h4unt3r
3

So führen Sie denselben Befehl für eine große Anzahl von Dateien im aktuellen Verzeichnis aus:

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

Dadurch wird die Datei customScriptfür jede txtDatei ausgeführt und die Ausgabe in outtxtDateien abgelegt. Ändern Sie nach Bedarf. Der Schlüssel, um dies zum Laufen zu bringen, ist die Signalverarbeitung mit SIGUSR1, damit der untergeordnete Prozess dem übergeordneten Prozess mitteilt, dass dies geschehen ist. Die Verwendung von SIGCHLD funktioniert nicht, da die meisten Anweisungen im Skript SIGCHLD-Signale für das Shell-Skript generieren. Ich habe versucht, diesen Befehl durch zu ersetzen sleep 1. Das Programm verwendete 0,28 s Benutzer-CPU und 0,14 s System-CPU. Dies war nur auf etwa 400 Dateien.

Arcege
quelle
Wie ist das "Warten" klug genug, um dieselbe Datei, über die gerade iteriert wird, zu übernehmen und die geschwisterliche "if" -Anweisung erneut einzugeben?
J Jones
Es ist nicht das wait, was schlau genug ist; aber es wird nach Erhalt des SIGUSR1Signals zurückkehren. Das Kind / der Arbeiter sendet ein SIGUSR1an das Elternteil, das abgefangen ( trap) wird, und dekrementiert $worker( trapKlausel) und kehrt abnormal zurück wait, wodurch die if [ $worker -lt $num_workers ]Klausel ausgeführt werden kann.
Arcege
0

Oder verwenden Sie einfach xargs -P, ohne zusätzliche Software installieren zu müssen:

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

Ein bisschen Erklärung für die Optionen:

  • -I'XXX' Legt die Zeichenfolge fest, die in der Befehlsvorlage durch den Dateinamen ersetzt wird
  • -P4 führt 4 Prozesse parallel aus
  • -n1 legt nur eine Datei pro Ausführung ab, obwohl zwei XXX gefunden werden
  • -print0 und -0 arbeiten Sie zusammen, sodass Sie Sonderzeichen (wie Leerzeichen) in den Dateinamen haben
Piotr Czapla
quelle