Vermeiden Sie diese Probleme, indem Sie die Ausführung von Bash-Skripten auf einmal beschränken
Wichtige Erkenntnisse
- Stellen Sie mit pgrep, lsof oder flock sicher, dass nur eine einzige Instanz Ihres Skripts ausgeführt wird, um Parallelitätsprobleme zu vermeiden.
- Implementieren Sie ganz einfach Prüfungen, um ein Skript selbst zu beenden, wenn andere laufende Instanzen erkannt werden.
- Durch die Nutzung der Befehle exec und env kann der Befehl flock all dies mit einer Codezeile erreichen.
Einige Linux-Skripte haben einen solchen Ausführungsaufwand, dass die gleichzeitige Ausführung mehrerer Instanzen verhindert werden muss. Zum Glück gibt es mehrere Möglichkeiten, dies in Ihren eigenen Bash-Skripten zu erreichen.
Manchmal ist einmal genug
Einige Skripte sollten nicht gestartet werden, wenn eine vorherige Instanz dieses Skripts noch ausgeführt wird. Wenn Ihr Skript übermäßig viel CPU-Zeit und RAM verbraucht oder viel Netzwerkbandbreite oder Festplattenüberlastung erzeugt, ist es sinnvoll, die Ausführung auf jeweils eine Instanz zu beschränken.
Aber es sind nicht nur Ressourcenfresser, die isoliert arbeiten müssen. Wenn Ihr Skript Dateien ändert, kann es zu einem Konflikt zwischen zwei (oder mehr) Instanzen des Skripts kommen, da diese um den Zugriff auf die Dateien streiten. Möglicherweise gehen Updates verloren oder die Datei ist beschädigt.
Eine Möglichkeit, diese Problemszenarien zu vermeiden, besteht darin, das Skript prüfen zu lassen, dass keine anderen Versionen von ihm selbst ausgeführt werden. Wenn andere laufende Kopien erkannt werden, beendet sich das Skript selbst.
Eine andere Technik besteht darin, das Skript so zu konstruieren, dass es sich beim Start selbst sperrt und so die Ausführung anderer Kopien verhindert.
Wir werden uns zwei Beispiele der ersten Technik ansehen und dann eine Möglichkeit betrachten, die zweite Technik umzusetzen.
Verwenden von pgrep, um Parallelität zu verhindern
Der Befehl pgrep durchsucht die Prozesse, die auf einem Linux-Computer ausgeführt werden, und gibt die Prozess-ID der Prozesse zurück, die dem Suchmuster entsprechen.
Ich habe ein Skript namens loop.sh. Es enthält eine for-Schleife, die die Iteration der Schleife ausgibt und dann eine Sekunde lang ruht. Dies geschieht zehnmal.
#!/bin/bash
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Ich habe zwei Instanzen davon ausgeführt und dann mit pgrep nach Namen gesucht.
pgrep loop.sh
Es lokalisiert die beiden Instanzen und meldet ihre Prozess-IDs. Wir können die Option -c (count) hinzufügen, damit pgrep die Anzahl der Instanzen zurückgibt.
pregp -c loop.sh
Wir können diese Anzahl von Instanzen in unserem Skript verwenden. Wenn der von pgrep zurückgegebene Wert größer als eins ist, muss mehr als eine Instanz ausgeführt werden und unser Skript wird geschlossen.
Wir erstellen ein Skript, das diese Technik verwendet. Wir nennen es pgrep-solo.sh.
Der if-Vergleich testet, ob die von pgrep zurückgegebene Zahl größer als eins ist. Wenn dies der Fall ist, wird das Skript beendet.
# count the instances of this script
if [ $(pgrep -c pgrep-solo.sh) -gt 1 ]; then
echo "Another instance of $0 is running. Stopping."
exit 1
fi
Wenn die von pgrep zurückgegebene Zahl eins ist, kann das Skript fortfahren. Hier ist das komplette Skript.
#!/bin/bash
echo "Starting."
# count the instances of this script
if [ $(pgrep -c pgrep-solo.sh) -gt 1 ]; then
echo "Another instance of $0 is running. Stopping."
exit 1
fi
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Kopieren Sie dies in Ihren bevorzugten Editor und speichern Sie es als pgrep-solo.sh. Dann machen Sie es mit chmod ausführbar.
chmod +x pgrep-loop.sh
Wenn es läuft, sieht es so aus.
./pgrep-solo.sh
Aber wenn ich versuche, es mit einer anderen Kopie zu starten, die bereits in einem anderen Terminalfenster läuft, erkennt es das und wird beendet.
./pgrep-solo.sh
Verwenden von lsof, um Parallelität zu verhindern
Mit dem Befehl lsof können wir etwas ganz Ähnliches tun.
Wenn wir die Option -t (terse) hinzufügen, listet lsof die Prozess-IDs auf.
lsof -t loop.sh
Wir können die Ausgabe von lsof an wc weiterleiten. Die Option -l (Zeilen) zählt die Anzahl der Zeilen, die in diesem Szenario mit der Anzahl der Prozess-IDs identisch ist.
lsof -t loop.sh | wc -l
Das können wir als Testgrundlage für den if-Vergleich in unserem Skript verwenden.
Speichern Sie diese Version als lsof-solo.sh.
#!/bin/bash
echo "Starting."
# count the instances of this script
if [ $(lsof -t "$0" | wc -l) -gt 1 ]; then
echo "Another instance of $0 is running. Stopping."
exit 1
fi
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Verwenden Sie chmod, um es ausführbar zu machen.
chmod +x lsof-solo.sh
Da das Skript lsof-solo.sh nun in einem anderen Terminalfenster ausgeführt wird, können wir keine zweite Kopie starten.
./lsof-solo.sh
Die pgrep-Methode erfordert nur einen einzigen Aufruf eines externen Programms (pgrep), die lsof-Methode erfordert zwei (lsof und wc). Der Vorteil der lsof-Methode gegenüber der pgrep-Methode besteht jedoch darin, dass Sie die Variable $0 im if-Vergleich verwenden können. Dies enthält den Skriptnamen.
Das bedeutet, dass Sie das Skript umbenennen können und es weiterhin funktioniert. Sie müssen nicht daran denken, die if-Vergleichszeile zu bearbeiten und den neuen Namen des Skripts einzufügen. Die Variable $0 enthält „./“ am Anfang des Skriptnamens (wie ./lsof-solo.sh), und pgrep gefällt das nicht.
Verwendung von Flock zur Verhinderung von Parallelität
Unsere dritte Technik verwendet den Befehl flock, der dazu dient, Datei- und Verzeichnissperren innerhalb von Skripts festzulegen. Während die Ressource gesperrt ist, kann kein anderer Prozess auf die gesperrte Ressource zugreifen.
Bei dieser Methode muss am Anfang Ihres Skripts eine einzelne Zeile hinzugefügt werden.
[ "${GEEKLOCK}" != "$0" ] && exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" || :
Wir werden diese Hieroglyphen in Kürze entschlüsseln. Schauen wir zunächst einmal, ob es funktioniert. Speichern Sie dieses unter flock-solo.sh.
#!/bin/bash
[ "${GEEKLOCK}" != "$0" ] && exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@" || :
echo "Starting."
# we're cleared for take off
for (( i=1; i<=10; i+=1 ))
do
echo "Loop:" $i
sleep 1
done
exit 0
Natürlich müssen wir es ausführbar machen.
chmod +x flock-solo.sh
Ich habe das Skript in einem Terminalfenster gestartet und dann versucht, es in einem anderen Terminalfenster erneut auszuführen.
./flock-solo
./flock-solo
./flock-solo
Ich kann das Skript erst starten, wenn die Instanz im anderen Terminalfenster abgeschlossen ist.
Lassen Sie uns die Zeile aufheben, die den Zauber bewirkt. Im Mittelpunkt steht das Herdenkommando.
flock -en "$0" "$0" "$@"
Der Befehl flock wird verwendet, um eine Datei oder ein Verzeichnis zu sperren und dann einen Befehl auszuführen. Die von uns verwendeten Optionen sind -e (exklusiv) und -n (nicht blockierend).
Die exklusive Option bedeutet, dass niemand anderes darauf zugreifen kann, wenn es uns gelingt, die Datei zu sperren. Die nicht blockierende Option bedeutet, dass wir sofort aufhören, es zu versuchen, wenn es uns nicht gelingt, eine Sperre zu erhalten. Wir versuchen es für eine gewisse Zeit nicht noch einmal, sondern verabschieden uns höflich.
Das erste $0 gibt die Datei an, die wir sperren möchten. Diese Variable enthält den Namen des aktuellen Skripts.
Das zweite $0 ist der Befehl, den wir ausführen möchten, wenn es uns gelingt, eine Sperre zu erhalten. Auch hier geben wir den Namen dieses Skripts weiter. Da die Sperre alle außer uns aussperrt, können wir die Skriptdatei starten.
Wir können Parameter an den gestarteten Befehl übergeben. Wir verwenden $@, um alle Befehlszeilenparameter, die an dieses Skript übergeben wurden, an den neuen Aufruf des Skripts zu übergeben, das von flock gestartet wird.
Also haben wir dieses Skript dazu gebracht, die Skriptdatei zu sperren und dann eine andere Instanz von sich selbst zu starten. Das ist fast das, was wir wollen, aber es gibt ein Problem. Wenn die zweite Instanz abgeschlossen ist, setzt das erste Skript seine Verarbeitung fort. Wie Sie sehen werden, haben wir jedoch noch einen weiteren Trick im Ärmel, um dem entgegenzuwirken.
Wir verwenden eine Umgebungsvariable namens GEEKLOCK, um anzugeben, ob ein laufendes Skript die Sperre anwenden muss oder nicht. Wenn das Skript gestartet wurde und keine Sperre vorhanden ist, muss die Sperre angewendet werden. Wenn das Skript gestartet wurde und eine Sperre vorhanden ist, muss es nichts tun, sondern kann einfach ausgeführt werden. Wenn ein Skript ausgeführt wird und die Sperre aktiviert ist, können keine anderen Instanzen des Skripts gestartet werden.
[ "${GEEKLOCK}" != "$0" ]
Dieser Test bedeutet „gibt true zurück, wenn die Umgebungsvariable GEEKLOCK nicht auf den Skriptnamen gesetzt ist.“ Der Test ist durch && (und) und || mit dem Rest des Befehls verkettet (oder). Der &&-Teil wird ausgeführt, wenn der Test „true“ zurückgibt, und der || Der Abschnitt wird ausgeführt, wenn der Test „Falsch“ zurückgibt.
env GEEKLOCK="$0"
Der Befehl env wird verwendet, um andere Befehle in geänderten Umgebungen auszuführen. Wir ändern unsere Umgebung, indem wir die Umgebungsvariable GEEKLOCK erstellen und sie auf den Namen des Skripts setzen. Der Befehl, den env starten wird, ist der Befehl flock, und der Befehl flock startet die zweite Instanz des Skripts.
Die zweite Instanz des Skripts führt eine Prüfung durch, um festzustellen, ob die Umgebungsvariable GEEKLOCK nicht vorhanden ist, stellt jedoch fest, dass sie vorhanden ist. Das || Der Abschnitt des Befehls wird ausgeführt, der nichts außer einem Doppelpunkt „:“ enthält, was eigentlich ein Befehl ist, der nichts bewirkt. Der Ausführungspfad verläuft dann durch den Rest des Skripts.
Aber wir haben immer noch das Problem, dass das erste Skript seine eigene Verarbeitung fortsetzt, wenn das zweite Skript beendet wurde. Die Lösung hierfür ist der Befehl exec. Dadurch werden andere Befehle ausgeführt, indem der aufrufende Prozess durch den neu gestarteten Prozess ersetzt wird.
exec env GEEKLOCK="$0" flock -en "$0" "$0" "$@"
Die vollständige Sequenz lautet also:
- Das Skript wird gestartet und kann die Umgebungsvariable nicht finden. Die &&-Klausel wird ausgeführt.
- exec startet env und ersetzt den ursprünglichen Skriptprozess durch den neuen env-Prozess.
- Der env-Prozess erstellt die Umgebungsvariable und startet flock.
- flock sperrt die Skriptdatei und startet eine neue Instanz des Skripts, die die Umgebungsvariable erkennt und || ausführt Klausel und das Skript kann zu Ende ausgeführt werden.
- Da das ursprüngliche Skript durch den Env-Prozess ersetzt wurde, ist es nicht mehr vorhanden und kann seine Ausführung nicht fortsetzen, wenn das zweite Skript beendet wird.
- Da die Skriptdatei während der Ausführung gesperrt ist, können keine anderen Instanzen gestartet werden, bis das von flock gestartete Skript nicht mehr ausgeführt wird und die Sperre aufhebt.
Das mag wie die Handlung von „Inception“ klingen, funktioniert aber wunderbar. Diese eine Zeile hat es auf jeden Fall in sich.
Zur Verdeutlichung: Es ist die Sperre der Skriptdatei, die den Start anderer Instanzen verhindert, nicht die Erkennung der Umgebungsvariablen. Die Umgebungsvariable weist ein laufendes Skript lediglich an, entweder die Sperre zu setzen oder dass die Sperre bereits vorhanden ist.
Sperren und Laden
Es ist einfacher, als Sie vielleicht erwarten, sicherzustellen, dass nur eine einzige Instanz eines Skripts gleichzeitig ausgeführt wird. Alle drei dieser Techniken funktionieren. Obwohl die Bedienung am kompliziertesten ist, lässt sich der Flock-Einzeiler am einfachsten in jedes Skript einfügen.