Manchmal müssen Sie sicherstellen, dass nur eine Instanz eines Shell-Skripts gleichzeitig ausgeführt wird.
Zum Beispiel ein Cron-Job, der über Crond ausgeführt wird und keine eigene Sperre bietet (z. B. der Standard-Solaris-Crond).
Ein gängiges Muster zum Implementieren von Sperren ist folgender Code:
#!/bin/sh
LOCK=/var/tmp/mylock
if [ -f $LOCK ]; then # 'test' -> race begin
echo Job is already running\!
exit 6
fi
touch $LOCK # 'set' -> race end
# do some work
rm $LOCK
Natürlich hat ein solcher Code eine Racebedingung. Es gibt ein Zeitfenster, in dem die Ausführung von zwei Instanzen nach Zeile 3 fortgesetzt werden kann, bevor eine die $LOCK
Datei berühren kann.
Für einen Cron-Job ist dies normalerweise kein Problem, da zwischen zwei Aufrufen ein Intervall von Minuten liegt.
Aber Dinge können schief gehen - zum Beispiel, wenn sich die Sperrdatei auf einem NFS-Server befindet - das hängt. In diesem Fall können mehrere Cron-Jobs in Zeile 3 blockiert und in die Warteschlange gestellt werden. Wenn der NFS-Server wieder aktiv ist, haben Sie eine donnernde Herde parallel laufender Jobs.
Beim Durchsuchen des Webs habe ich das Tool Lockrun gefunden, das eine gute Lösung für dieses Problem zu sein scheint. Damit führen Sie ein Skript aus, das wie folgt gesperrt werden muss:
$ lockrun --lockfile=/var/tmp/mylock myscript.sh
Sie können dies in eine Hülle packen oder von Ihrer Crontab verwenden.
Es verwendet lockf()
(POSIX), falls verfügbar, und greift auf flock()
(BSD) zurück. Und lockf()
Unterstützung über NFS sollte relativ weit verbreitet sein.
Gibt es Alternativen zu lockrun
?
Was ist mit anderen Cron-Daemons? Gibt es gewöhnliche Kronen, die eine sichere Verriegelung unterstützen? Ein kurzer Blick in die Manpage von Vixie Crond (Standard auf Debian / Ubuntu-Systemen) zeigt nichts über das Sperren.
Wäre es eine gute Idee, ein Tool wie lockrun
in coreutils aufzunehmen ?
Meiner Meinung nach implementiert es ein Thema sehr ähnlich timeout
, nice
und Freunde.
kill
. und es scheint eine gute Praxis zu sein, die eigene PID in der Schlossdatei zu speichern, anstatt sie nur zu berühren.Antworten:
Hier ist eine andere Möglichkeit, das Sperren in einem Shell-Skript durchzuführen, um die oben beschriebene Race-Bedingung zu verhindern, bei der zwei Jobs möglicherweise Zeile 3 passieren. Die
noclobber
Option funktioniert in ksh und bash. Nicht verwenden,set noclobber
da Sie kein Scripting in csh / tcsh ausführen sollten. ;)YMMV mit Sperrung für NFS (Sie wissen, wenn NFS-Server nicht erreichbar sind), aber im Allgemeinen ist es viel robuster als früher. (vor 10 Jahren)
Wenn Sie Cron-Jobs haben, die zur gleichen Zeit auf mehreren Servern dasselbe tun, aber nur eine Instanz zur Ausführung benötigen, funktioniert dies möglicherweise für Sie.
Ich habe keine Erfahrung mit Lockrun, aber es könnte hilfreich sein, eine voreingestellte Sperrumgebung zu haben, bevor das Skript tatsächlich ausgeführt wird. Oder vielleicht auch nicht. Sie setzen lediglich den Test für die Sperrdatei außerhalb Ihres Skripts in einem Wrapper und könnten theoretisch nicht dieselbe Race-Bedingung erfüllen, wenn zwei Jobs genau zur gleichen Zeit von lockrun aufgerufen würden, genau wie bei 'inside- the-script 'lösung?
Das Sperren von Dateien ist sowieso ein ehrenwertes Verhalten des Systems, und alle Skripte, die vor der Ausführung nicht auf das Vorhandensein der Sperrdatei prüfen, tun das, was sie tun werden. Indem Sie den Lockfile-Test und das richtige Verhalten durchführen, lösen Sie 99% der potenziellen Probleme, wenn nicht sogar 100%.
Wenn Sie häufig mit Lockfile-Rennen konfrontiert sind, kann dies ein Hinweis auf ein größeres Problem sein, z. B. dass Ihre Jobs nicht richtig terminiert sind, oder wenn das Intervall nicht so wichtig ist wie die Beendigung des Jobs, ist Ihr Job möglicherweise besser für die Dämonisierung geeignet .
BEARBEITEN UNTEN - 2016-05-06 (wenn Sie KSH88 verwenden)
Basieren Sie auf @Clint Pachls Kommentar unten, wenn Sie ksh88 verwenden, verwenden Sie
mkdir
anstelle vonnoclobber
. Dies mindert meistens eine potenzielle Rennbedingung, schränkt sie jedoch nicht vollständig ein (obwohl das Risiko winzig ist). Für weitere Informationen lesen Sie den Link, den Clint unten gepostet hat .Ein zusätzlicher Vorteil ist, dass Sie, wenn Sie in Ihrem Skript tmpfiles erstellen müssen, das
lockdir
Verzeichnis für diese Dateien verwenden können, da Sie wissen, dass diese beim Beenden des Skripts bereinigt werden.Für modernere Bash sollte die Noclobber-Methode oben geeignet sein.
quelle
lockf()
Systemaufruf - wenn es gesichert ist, werden alle Prozesse fortgesetzt, aber nur ein Prozess gewinnt die Sperre. Keine Racebedingung. Ich stoße bei Cronjobs nicht oft auf solche Probleme - das Gegenteil ist der Fall - aber dies ist ein Problem, wenn es Sie trifft, kann es sehr schmerzhaft sein.set -o noclobber && echo "$$" > "$lockfile"
um einen sicheren Fallback zu erhalten, wenn die Shell die Option noclobber nicht unterstützt.noclobber
Option ist möglicherweise anfällig für Rennbedingungen. Unter mywiki.wooledge.org/BashFAQ/045 finden Sie einige Denkanstöße.noclobber
(oder-C
) in ksh88 funktioniert nicht, da ksh88 nichtO_EXCL
für verwendet wirdnoclobber
. Wenn Sie mit einer neueren Shell arbeiten, sind Sie möglicherweise in Ordnung ...Ich bevorzuge harte Links.
Harte Links sind atomar über NFS und zum größten Teil auch über mkdir . Verwenden
mkdir(2)
oderlink(2)
sind auf praktischer Ebene ungefähr gleich; Ich bevorzuge nur die Verwendung von Hardlinks, da mehr Implementierungen von NFS atomare Hardlinks als atomare zuließenmkdir
. Mit modernen Versionen von NFS sollten Sie sich auch keine Sorgen mehr machen müssen.quelle
Ich verstehe, dass
mkdir
das atomar ist, also vielleicht:quelle
mkdir()
über NFS (> = 3) standardisiert ist, atomar zu sein.mkdir
auf, atomar zu sein (es tut es fürrename
). In der Praxis ist bekannt, dass einige Implementierungen dies nicht sind. Verwandte: ein interessanter Thread, einschließlich eines Beitrags des Autors von GNU Arch .Eine einfache Möglichkeit ist die Verwendung,
lockfile
die normalerweise mit demprocmail
Paket geliefert wird.quelle
sem
Das, was als Teil der GNU-parallel
Tools kommt, könnte das sein, wonach Sie suchen:Wie in:
Ausgabe:
Bitte beachten Sie, dass die Bestellung nicht garantiert wird. Auch die Ausgabe wird erst am Ende angezeigt (irritierend!). Trotzdem ist es die präziseste Methode, die ich kenne, um sich vor gleichzeitiger Ausführung zu schützen, ohne sich um Sperrdateien, Wiederholungsversuche und Bereinigung zu sorgen.
quelle
sem
Griff gebotene Verriegelung während der Ausführung abgeschossen?Ich benutze
dtach
.quelle
Ich verwende das Befehlszeilentool "flock", um Sperren in meinen Bash-Skripten zu verwalten, wie hier und hier beschrieben . Ich habe diese einfache Methode aus der Flock-Manpage verwendet, um einige Befehle in einer Subshell auszuführen ...
In diesem Beispiel schlägt der Exit-Code 1 fehl, wenn die Sperrdatei nicht abgerufen werden kann. Flock kann aber auch so verwendet werden, dass keine Befehle in einer Sub-Shell ausgeführt werden müssen :-)
quelle
flock()
Systemaufruf funktioniert nicht über NFS .flock()
und ist damit problematisch über NFS. Übrigens greift flock () unter Linux inzwischen auffcntl()
Dateien zurück, die sich auf einem NFS-Mount befinden. In einer reinen Linux-NFS-Umgebungflock()
funktioniert dies nun über NFS.Benutze keine Datei.
Wenn Ihr Skript so ausgeführt wird, zB:
Sie können erkennen, ob es ausgeführt wird, indem Sie Folgendes verwenden:
quelle
my_script
? Falls eine andere Instanz ausgeführt wird - enthält sie nichtrunning_proc
zwei übereinstimmende Zeilen? Ich mag die Idee, aber natürlich - Sie werden falsche Ergebnisse erhalten, wenn ein anderer Benutzer ein Skript mit dem gleichen Namen$!
statt$$
in Ihrem Beispiel verwenden.grep -v $$
) ändern . Grundsätzlich habe ich versucht, eine andere Herangehensweise an das Problem zu finden.Für die tatsächliche Verwendung sollten Sie die am besten bewertete Antwort verwenden .
Ich möchte jedoch einige verschiedene kaputte und teilweise umsetzbare Ansätze
ps
und die vielen Vorbehalte, die sie haben, diskutieren , da ich immer wieder sehe, wie Leute sie verwenden.Diese Antwort ist wirklich die Antwort auf "Warum nicht das Sperren in der Shell verwenden
ps
undgrep
damit umgehen?"Unterbrochener Ansatz # 1
Zunächst ein Ansatz in einer anderen Antwort , der ein paar positive Stimmen hat, obwohl er nicht funktioniert (und niemals funktionieren könnte) und eindeutig niemals getestet wurde:
Korrigieren wir die Syntaxfehler und die fehlerhaften
ps
Argumente und erhalten:Dieses Skript wird immer beendet , unabhängig davon, wie Sie es ausführen.
Wenn Sie es mit ausführen
./myscript
,ps
wird nur die Ausgabe angezeigt12345 -bash
, die nicht mit der erforderlichen Zeichenfolge übereinstimmt12345 bash ./myscript
, sodass dies fehlschlägt.Wenn Sie es mit ausführen
bash myscript
, werden die Dinge interessanter. Der Bash-Prozess fordert zum Ausführen der Pipeline auf, und die untergeordnete Shell führt dasps
und ausgrep
. Sowohl die ursprüngliche Shell als auch die untergeordnete Shell werden in derps
Ausgabe wie folgt angezeigt :Das ist nicht die erwartete Ausgabe
$$ bash $0
, daher wird Ihr Skript beendet.Unterbrochene Annäherung # 2
Nun habe ich dem Benutzer, der den fehlerhaften Ansatz # 1 geschrieben hat, etwas Ähnliches angetan, als ich das erste Mal versuchte:
Das funktioniert fast . Aber die Tatsache, dass man sich gabelt, um die Pfeife laufen zu lassen, wirft dies weg. Also wird dieser auch immer aussteigen.
Unzuverlässiger Ansatz # 3
Diese Version vermeidet das Pipeline-Forking-Problem in Ansatz 2, indem zuerst alle PIDs mit dem aktuellen Skript in den Befehlszeilenargumenten abgerufen und dann diese PID-Liste separat gefiltert werden, um die PID des aktuellen Skripts wegzulassen.
Dies könnte funktionieren ... vorausgesetzt, kein anderer Prozess verfügt über eine entsprechende Befehlszeile
$0
und das Skript wird immer auf die gleiche Weise aufgerufen (z. B. wenn es mit einem relativen Pfad und dann einem absoluten Pfad aufgerufen wird, bemerkt die letztere Instanz den ersteren nicht ).Unzuverlässiger Ansatz # 4
Was ist, wenn wir die Überprüfung der vollständigen Befehlszeile überspringen, da dies möglicherweise nicht auf ein aktuell ausgeführtes Skript hinweist, und
lsof
stattdessen überprüfen, ob alle Prozesse mit diesem Skript geöffnet sind?Na ja, dieser Ansatz ist eigentlich gar nicht so schlecht:
Wenn eine Kopie des Skripts ausgeführt wird, wird die neue Instanz natürlich einwandfrei gestartet, und es werden zwei Kopien ausgeführt.
Oder wenn das ausgeführte Skript geändert wird (z. B. mit Vim oder mit a
git checkout
), startet die "neue" Version des Skripts problemlos, da sowohl Vim als auchgit checkout
eine neue Datei (ein neuer Inode) anstelle des altes.Allerdings , wenn das Skript nie geändert wird und nie kopiert, dann ist diese Version ziemlich gut. Es gibt keine Racebedingung, da die Skriptdatei bereits geöffnet sein muss, bevor die Prüfung erreicht werden kann.
Es kann immer noch Fehlalarme geben, wenn die Skriptdatei in einem anderen Prozess geöffnet ist. Beachten Sie jedoch, dass vim die Skriptdatei auch dann nicht geöffnet hält, wenn sie in Vim bearbeitet werden kann, sodass keine Fehlalarme auftreten.
Denken Sie jedoch daran, dass Sie diesen Ansatz nicht verwenden sollten, wenn das Skript bearbeitet oder kopiert werden könnte, da Sie falsche Negative erhalten, dh mehrere Instanzen gleichzeitig ausgeführt werden für dich. Ich erwähne es jedoch, weil Ansatz Nr. 3 falsch positive Ergebnisse liefert (dh den Start verweigert), wenn Sie das Skript mit Vim geöffnet haben.
Also, was ist dann zu tun?
Die am häufigsten gewählte Antwort auf diese Frage liefert einen soliden Ansatz.
Vielleicht können Sie eine bessere schreiben ... aber wenn Sie nicht alle Probleme und Vorbehalte bei den oben genannten Ansätzen verstehen, ist es unwahrscheinlich, dass Sie eine Sperrmethode schreiben, die sie alle vermeidet.
quelle
Mit dem FLOM-Tool (Free LOck Manager) ist das Serialisieren von Befehlen so einfach wie das Ausführen
Mit FLOM können Sie komplexere Anwendungsfälle (verteiltes Sperren, Lese- / Schreibvorgänge, numerische Ressourcen usw.) implementieren, wie hier erläutert: http://sourceforge.net/p/flom/wiki/FLOM%20by%20examples/
quelle
Hier ist etwas, das ich manchmal auf einem Server hinzufüge, um die Rennbedingungen für jeden Job auf der Maschine einfach zu handhaben. Es ähnelt dem Beitrag von Tim Kennedy, aber auf diese Weise erhalten Sie die Rennhandhabung, indem Sie jedem Bash-Skript, das es benötigt, nur eine Zeile hinzufügen.
Fügen Sie den folgenden Inhalt in zB / opt / racechecker / racechecker ein:
Hier ist, wie man es benutzt. Beachten Sie die Zeile nach dem Schebang:
Die Art und Weise, wie es funktioniert, ist, dass es den Namen der Haupt-BashScript-Datei herausfindet und eine PID-Datei unter "/ tmp" erstellt. Außerdem wird dem Endsignal ein Listener hinzugefügt. Der Listener entfernt die PID-Datei, wenn das Hauptskript ordnungsgemäß abgeschlossen ist.
Wenn beim Starten einer Instanz eine PID-Datei vorhanden ist, wird stattdessen die if-Anweisung ausgeführt, die den Code in der zweiten if-Anweisung enthält. In diesem Fall habe ich beschlossen, in diesem Fall eine Alarmmail zu starten.
Was ist, wenn das Skript abstürzt?
Eine weitere Übung wäre, mit Abstürzen umzugehen. Idealerweise sollte die PID-Datei entfernt werden, auch wenn das Hauptskript aus irgendeinem Grund abstürzt. Dies ist in meiner obigen Version nicht der Fall. Das heißt, wenn das Skript abstürzt, muss die PID-Datei manuell entfernt werden, um die Funktionalität wiederherzustellen.
Im Falle eines Systemabsturzes
Es ist eine gute Idee, die PID-Datei / Sperrdatei unter zum Beispiel / tmp zu speichern. Auf diese Weise werden Ihre Skripte nach einem Systemabsturz definitiv weiter ausgeführt, da die PID-Dateien beim Booten immer gelöscht werden.
quelle
Überprüfen Sie mein Skript ...
Du darfst es LIEBEN ...
quelle
Ich biete die folgende Lösung in einem Skript mit dem Namen "flocktest" an
quelle