Website-Suche

Multithreaded Xargs mit Beispielen


Wenn Sie neu bei xargs sind, oder noch nicht wissen, was xargs ist, lesen Sie bitte zuerst unsere xargs für Anfänger mit Beispielen. Wenn Sie bereits etwas an xargs gewöhnt sind und grundlegende xargs-Befehlszeilenanweisungen schreiben können, ohne einen Blick in das Handbuch zu werfen, dann wird Ihnen dieser Artikel helfen, mit xargs auf der Befehlszeile fortgeschrittener zu werden, insbesondere indem er es multithreaded macht.

In diesem Tutorial lernen Sie:

  • So verwenden Sie xargs -P (Multithread-Modus) über die Befehlszeile in Bash
  • Erweiterte Anwendungsbeispiele mit Multithreaded xargs über die Befehlszeile in Bash
  • Ein tieferes Verständnis dafür, wie Sie xargs Multithreaded auf Ihren vorhandenen Bash-Code anwenden können

Multithreaded Xargs mit Beispielen

Verwendete Softwareanforderungen und Konventionen

Beispiel 1: Aufrufen einer anderen Bash-Shell mit xargs-kompilierter Eingabe

Nachdem man xargs gelernt hat, wird man bald feststellen, dass – während xargs es einem ermöglicht, viele mächtige Dinge selbst zu tun – die Macht von xargs durch seine Unfähigkeit, mehrere Befehle nacheinander auszuführen, begrenzt zu sein scheint.

Nehmen wir zum Beispiel an, wir haben ein Verzeichnis mit Unterverzeichnissen mit den Namen 00 bis 10 (insgesamt 11). Und für jedes dieser Unterverzeichnisse wollen wir in dieses Verzeichnisse eintauchen und prüfen, ob eine Datei mit dem Namen file.txt existiert, und wenn ja, den Inhalt dieser Datei in eine Datei verschieben (und mit >> zusammenführen), die sich in dem Verzeichnis total_file.txt befindet, in dem sich die Verzeichnisse 00 bis 10 befinden. Versuchen wir, dies mit xargs in verschiedenen Schritten zu tun:

mkdir 00 01 02 03 04 05 06 07 08 09 10
ls
00  01  02  03  04  05  06  07  08  09  10
echo 'a' > 03/file.txt
echo 'b' > 07/file.txt
echo 'c' > 10/file.txt

Hier legen wir zunächst 11 Verzeichnisse an, 00 bis 10 und erstellen als nächstes 3 Beispiel-file.txt-Dateien in den Unterverzeichnissen 03, 07 und 10.

find . -maxdepth 2 -type f -name file.txt
./10/file.txt
./07/file.txt
./03/file.txt

Wir schreiben dann einen find-Befehl, um alle file.txt Dateien beginnend mit dem aktuellen Verzeichnis (.) und das bis zu einem Maximum von 1 Ebene von Unterverzeichnissen zu finden:

find . -maxdepth 2 -type f -name file.txt | xargs -I{} cat {} > ./total_file.txt
cat total_file.txt
c
b
a

-maxdepth 2 gibt das aktuelle Verzeichnis (1) und alle Unterverzeichnisse dieses Verzeichnisses an (daher die maxdepth von 2).

Schließlich verwenden wir xargs (mit dem empfohlenen und bevorzugten {}-Ersetzungsstring, wie er an die Option xargs -I replace string übergeben wird), um den Inhalt einer solchen Datei, die mit dem Befehl find gefunden wurde, in eine Datei im aktuellen Verzeichnis mit dem Namen total_file.txt zu caten.

Schön zu beachten ist hier, dass, obwohl man sich xargs als nachträgliches Ausführen mehrerer Cat-Befehle vorstellen würde, die alle auf dieselbe Datei umleiten, man > verwenden kann (Ausgabe in eine neue Datei, Erstellen der Datei, wenn sie noch nicht existiert, und Überschreiben jeder Datei mit demselben Namen, die bereits vorhanden ist) anstelle von >> (an eine Datei anhängen, und erstellen Sie die Datei, falls noch nicht vorhanden)!

Die Übung hat bisher unsere Anforderungen irgendwie erfüllt, aber sie hat nicht genau mit der Anforderung entsprochen – nämlich sie wandert nicht in die Unterverzeichnisse. Es wurde auch nicht die >> Umleitung wie angegeben verwendet, obwohl die Verwendung dieser in diesem Fall immer noch funktioniert hätte.

Die Herausforderung beim Ausführen mehrerer Befehle (wie dem spezifischen cd-Befehl, der erforderlich ist, um das Verzeichnis/die Traverse in das Unterverzeichnis zu ändern) aus xargs heraus besteht darin, dass 1) sie sehr schwer zu codieren sind und 2) es möglicherweise überhaupt nicht möglich ist, dies zu codieren.

Es gibt jedoch eine andere und leicht verständliche Möglichkeit, dies zu codieren, und sobald Sie wissen, wie es geht, werden Sie dies wahrscheinlich in ausreichender Menge verwenden. Lassen Sie uns eintauchen.

rm total_file.txt

Zuerst haben wir unsere vorherige Ausgabe bereinigt.

ls -d --color=never [0-9][0-9] | xargs -I{} echo 'cd {}; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi'
cd 00; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 01; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 02; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 03; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 04; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 05; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 06; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 07; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 08; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 09; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi
cd 10; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi

Als nächstes haben wir einen Befehl formuliert, diesmal mit ls, der alle Verzeichnisse auflistet, die dem regulären Ausdruck [0-9][0-9] entsprechen (Lesen Sie unseren Artikel Advanced Bash regex mit Beispielen für weitere Informationen zu regulären Ausdrücken).

Wir haben auch xargs verwendet, aber diesmal (im Vergleich zu früheren Beispielen) mit einem echo-Befehl, der genau das ausgibt, was wir gerne tun würden, auch wenn es mehr als einen oder mehrere Befehle erfordert. Stellen Sie sich das wie ein Mini-Skript vor.

Wir verwenden auch cd {}, um in Verzeichnisse zu wechseln, wie sie mit dem Befehl ls -d (nur Verzeichnisse) aufgelistet sind (der nebenbei bemerkt durch die --color=never Klausel geschützt ist, die verhindert, dass Farbcodes in der ls-Ausgabe unsere Ergebnisse verzerren), und überprüfen, ob die Datei file.txt im Unterverzeichnis vorhanden ist, indem wir den Befehl if [ -r ... verwenden. Wenn es existiert, kategorisieren wir die file.txt in .. /total_file.txt. Beachten Sie das .., da das cd {} im Befehl uns in das Unterverzeichnis verschoben hat!

Wir lassen das laufen, um zu sehen, wie es funktioniert (schließlich wird nur das Echo ausgeführt, es passiert eigentlich nichts). Der generierte Code sieht gut aus. Gehen wir jetzt noch einen Schritt weiter und führen sie tatsächlich aus:

ls -d --color=never [0-9][0-9] | xargs -I{} echo 'cd {}; if [ -r ./file.txt ]; then cat file.txt >> ../total_file.txt; fi' | xargs -I{} bash -c "{}"
cat total_file.txt
a
b
c

Wir haben nun das gesamte Skript mit einem spezifischen (und immer gleichen, d.h. Sie werden sich dabei ertappen, wie Sie mit einiger Regelmäßigkeit | xargs -I{} bash -c "{}" schreiben) Befehl ausführt, der das ausführt, was durch das vorhergehende Echo erzeugt wurde: xargs -I{} bash -c "{}". Im Grunde genommen wird der Bash-Interpreter angewiesen, das auszuführen, was an ihn übergeben wurde – und dies für jeden generierten Code. Sehr leistungsstark!

Beispiel 2: Multithreaded xargs

Hier werfen wir einen Blick auf zwei verschiedene xargs-Befehle, von denen der eine ohne parallele (Multithreaded) Ausführung ausgeführt wird, der andere mit. Betrachten Sie den Unterschied zwischen den folgenden beiden Beispielen:

time for i in $(seq 1 5); do echo $[$RANDOM % 5 + 1]; done | xargs -I{} echo "sleep {}; echo 'Done! {}'" | xargs -I{} bash -c "{}"
Done! 5
Done! 5
Done! 2
Done! 4
Done! 1

real    0m17.016s
user    0m0.017s
sys 0m0.003s
time for i in $(seq 1 5); do echo $[$RANDOM % 5 + 1]; done | xargs -I{} echo "sleep {}; echo 'Done! {}'" | xargs -P5 -I{} bash -c "{}"
Done! 1
Done! 3
Done! 3
Done! 3
Done! 5

real    0m5.019s
user    0m0.036s
sys 0m0.015s

Der Unterschied zwischen den beiden tatsächlichen Befehlszeilen ist gering. wir haben nur -P5 in der zweiten Befehlszeile hinzugefügt. Die Laufzeit (gemessen am time-Befehlspräfix) ist jedoch signifikant. Lassen Sie uns herausfinden, warum (und warum die Ausgabe unterschiedlich ist!).

Im ersten Beispiel erstellen wir eine for-Schleife, die 5 Mal ausgeführt wird (da die Subshell $ (seq 1 5) Zahlen von 1 bis 5 generiert) und darin eine Zufallszahl zwischen 1 und 5 wiedergibt. Als nächstes haben wir, ähnlich wie im letzten Beispiel, diese Ausgabe in den Schlafbefehl gesendet und auch die Dauer des Schlafs als Teil des Done! Echo. Schließlich haben wir dies so geschickt, dass es von einem Subshell-Bash-Befehl ausgeführt wird, wieder auf ähnliche Weise wie in unserem letzten Beispiel.

Die Ausgabe des ersten Befehls funktioniert folgendermaßen: Ausführen eines Ruhezustands, Ausgabe des Ergebnisses, Ausführen des nächsten Ruhezustands usw.

Der zweite Befehl ändert dies jedoch vollständig. Hier haben wir -P5 hinzugefügt, das im Grunde 5 parallele Threads auf einmal startet!

Dieser Befehl funktioniert folgendermaßen: Starten Sie bis zu x Threads (wie durch die Option -P definiert) und verarbeiten Sie sie gleichzeitig. Wenn ein Thread abgeschlossen ist, rufen Sie sofort neue Eingaben ab, und warten Sie nicht, bis andere Threads zuerst abgeschlossen sind. Der letzte Teil dieser Beschreibung ist hier nicht anwendbar (dies wäre nur der Fall, wenn weniger mit -P angegebene Threads als die angegebene Anzahl von Eingabezeilen angegeben wären, oder mit anderen Worten, es wären weniger parallele Threads verfügbar als die Anzahl der Eingabezeilen).

Das Ergebnis ist, dass die Threads, die zuerst beendet werden – diejenigen mit einer kurzen zufälligen Ruhezeit – zuerst zurückkommen und ihre "Fertig!"-Anweisung ausgeben. Auch die Gesamtlaufzeit sinkt von ca. 17 Sekunden auf nur noch ca. 5 Sekunden exakt in Echtzeit. Kühl!

Schlussfolgerung

Die Verwendung von xargs ist eine der fortschrittlichsten und auch eine der leistungsstärksten Möglichkeiten, in Bash zu programmieren. Aber es hört nicht bei der reinen Verwendung von xargs auf! In diesem Artikel haben wir daher die parallele Ausführung mehrerer Threads über die Option -P für xargs untersucht. Wir haben uns auch das Aufrufen von Subshells mit $() angesehen und schließlich eine Methode eingeführt, um Multi-Command-Anweisungen direkt an xargs zu übergeben, indem wir einen bash -c subshell-Aufruf verwenden.

Stark? Wir finden ja! Hinterlassen Sie uns Ihre Gedanken.

Verwandte Artikel: