Schnelle und schmutzige Methode, um sicherzustellen, dass jeweils nur eine Instanz eines Shell-Skripts ausgeführt wird

Antworten:

109

Hier ist eine Implementierung, die eine Sperrdatei verwendet und eine PID darin wiedergibt . Dies dient als Schutz, wenn der Prozess vor dem Entfernen der PID-Datei abgebrochen wird :

LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "already running"
    exit
fi

# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}

# do stuff
sleep 1000

rm -f ${LOCKFILE}

Der Trick hier ist der, kill -0der kein Signal liefert, sondern nur prüft, ob ein Prozess mit der angegebenen PID existiert. Durch den Aufruf von trapwird auch sichergestellt, dass die Sperrdatei auch dann entfernt wird, wenn Ihr Prozess beendet wird (außer kill -9).

bmdhacks
quelle
73
Wie bereits in einem Kommentar zu einer anderen Antwort erwähnt, hat dies einen schwerwiegenden Fehler. Wenn das andere Skript zwischen der Prüfung und dem Echo gestartet wird, stoßen Sie an.
Paul Tomblin
1
Der Symlink-Trick ist ordentlich, aber wenn der Besitzer der Sperrdatei kill -9'd ist oder das System abstürzt, gibt es immer noch eine Race-Bedingung, um den Symlink zu lesen, festzustellen, dass der Besitzer weg ist, und ihn dann zu löschen. Ich bleibe bei meiner Lösung.
bmdhacks
9
Atomic Check and Create ist in der Shell entweder mit flock (1) oder lockfile (1) verfügbar. Siehe andere Antworten.
dmckee --- Ex-Moderator Kätzchen
3
In meiner Antwort finden Sie eine tragbare Möglichkeit, eine Atomprüfung durchzuführen und zu erstellen, ohne sich auf Dienstprogramme wie Flock oder Lockfile verlassen zu müssen.
lhunath
2
Dies ist nicht atomar und daher nutzlos. Sie benötigen einen atomaren Mechanismus für test & set.
K Richard Pixley
214

Verwenden Sie flock(1)diese Option, um eine exklusive Gültigkeitsbereichssperre zu einem On-File-Deskriptor zu machen. Auf diese Weise können Sie sogar verschiedene Teile des Skripts synchronisieren.

#!/bin/bash

(
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200 || exit 1

  # Do stuff

) 200>/var/lock/.myscript.exclusivelock

Dies stellt sicher, dass Code zwischen (und jeweils )nur von einem Prozess ausgeführt wird und dass der Prozess nicht zu lange auf eine Sperre wartet.

Vorsichtsmaßnahme: Dieser spezielle Befehl ist ein Teil von util-linux. Wenn Sie ein anderes Betriebssystem als Linux ausführen, ist es möglicherweise nicht verfügbar.

Alex B.
quelle
11
Was ist die 200? In der Manul steht "fd", aber ich weiß nicht, was das bedeutet.
Chovy
4
@chovy "Dateideskriptor", ein ganzzahliges Handle, das eine geöffnete Datei kennzeichnet.
Alex B
6
Wenn sich jemand wundert: Die Syntax ( command A ) command Bruft eine Subshell für auf command A. Dokumentiert unter tldp.org/LDP/abs/html/subshells.html . Ich bin mir immer noch nicht sicher, wann die Unterschale und das Kommando B aufgerufen werden sollen.
Dr. Jan-Philip Gehrcke
1
Ich denke, dass der Code in der Sub-Shell eher so aussehen sollte: if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fiWenn das Timeout auftritt (bei einem anderen Prozess ist die Datei gesperrt), ändert dieses Skript die Datei nicht. Wahrscheinlich ... lautet das Gegenargument "aber wenn es 10 Sekunden gedauert hat und die Sperre immer noch nicht verfügbar ist, wird sie niemals verfügbar sein", vermutlich weil der Prozess, der die Sperre hält, nicht beendet wird (möglicherweise wird sie ausgeführt unter einem Debugger?).
Jonathan Leffler
1
Die umgeleitete Datei ist nur ein Platzordner, auf den die Sperre einwirken kann. Es werden keine aussagekräftigen Daten gespeichert. Das exitist aus dem Teil innerhalb der ( ). Wenn der Unterprozess endet, wird die Sperre automatisch aufgehoben, da kein Prozess sie hält.
Clacke
158

Alle Ansätze, die die Existenz von "Sperrdateien" testen, sind fehlerhaft.

Warum? Weil es keine Möglichkeit gibt, zu überprüfen, ob eine Datei vorhanden ist, und sie in einer einzelnen atomaren Aktion zu erstellen. Aus diesem Grund; Es gibt eine Rennbedingung, die Ihre Versuche, sich gegenseitig auszuschließen , zum Erliegen bringt .

Stattdessen müssen Sie verwenden mkdir. mkdirErstellt ein Verzeichnis, falls es noch nicht vorhanden ist, und legt einen Exit-Code fest. Noch wichtiger ist, dass dies alles in einer einzigen atomaren Aktion erledigt wird, was es perfekt für dieses Szenario macht.

if ! mkdir /tmp/myscript.lock 2>/dev/null; then
    echo "Myscript is already running." >&2
    exit 1
fi

Alle Details finden Sie im ausgezeichneten BashFAQ: http://mywiki.wooledge.org/BashFAQ/045

Wenn Sie sich um veraltete Schlösser kümmern möchten, ist die Fixiereinheit (1) praktisch. Der einzige Nachteil hierbei ist, dass der Vorgang ungefähr eine Sekunde dauert, also nicht sofort.

Hier ist eine Funktion, die ich einmal geschrieben habe und die das Problem mit der Fixiereinheit löst:

#       mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file.  To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
    local file=$1 pid pids 

    exec 9>>"$file"
    { pids=$(fuser -f "$file"); } 2>&- 9>&- 
    for pid in $pids; do
        [[ $pid = $$ ]] && continue

        exec 9>&- 
        return 1 # Locked by a pid.
    done 
}

Sie können es in einem Skript wie folgt verwenden:

mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }

Wenn Sie sich nicht für Portabilität interessieren (diese Lösungen sollten auf so ziemlich jeder UNIX-Box funktionieren), bietet die Linux- Fixiereinheit (1) einige zusätzliche Optionen und es gibt auch eine Herde (1) .

lhunath
quelle
1
Sie können das if ! mkdirTeil mit der Überprüfung kombinieren , ob der Prozess mit der PID, die (beim erfolgreichen Start) im Lockdir gespeichert ist, tatsächlich ausgeführt wird und mit dem Skript für den Schutz der Stalenes identisch ist. Dies würde auch vor einer Wiederverwendung der PID nach einem Neustart schützen und nicht einmal erfordern fuser.
Tobias Kienzler
4
Es ist sicherlich richtig , dass mkdirnicht ist , definiert eine atomare Operation zu sein , und als solche , dass „Nebenwirkung“ ist eine Implementierung Detail des Dateisystems. Ich glaube ihm voll und ganz, wenn er sagt, dass NFS es nicht atomar umsetzt. Obwohl ich nicht vermute /tmp, dass Sie eine NFS-Freigabe sind und wahrscheinlich von einem fs bereitgestellt werden, das mkdiratomar implementiert wird .
lhunath
5
Es gibt jedoch eine Möglichkeit, die Existenz einer regulären Datei zu überprüfen und atomar zu erstellen, wenn dies nicht der Fall ist: Verwenden Sie diese Option ln, um einen festen Link aus einer anderen Datei zu erstellen. Wenn Sie seltsame Dateisysteme haben, die dies nicht garantieren, können Sie anschließend den Inode der neuen Datei überprüfen, um festzustellen, ob er mit der Originaldatei identisch ist.
Juan Cespedes
4
Es gibt eine Möglichkeit, zu überprüfen, ob eine Datei vorhanden ist, und sie in einer einzigen atomaren Aktion zu erstellen open(... O_CREAT|O_EXCL). Dazu benötigen Sie lediglich ein geeignetes Anwenderprogramm wie lockfile-create(in lockfile-progs) oder dotlockfile(in liblockfile-bin). Und stellen Sie sicher, dass Sie ordnungsgemäß aufräumen (z. B. trap EXIT) oder auf veraltete Schlösser prüfen (z --use-pid. B. mit ).
Toby Speight
5
"Alle Ansätze, die die Existenz von" Sperrdateien "testen, sind fehlerhaft. Warum? Weil es keine Möglichkeit gibt, zu überprüfen, ob eine Datei vorhanden ist, und sie in einer einzigen atomaren Aktion zu erstellen." - Um sie atomar zu machen, muss sie bei ausgeführt werden die Kernel-Ebene - und dies geschieht auf Kernel-Ebene mit flock (1) linux.die.net/man/1/flock, das ab dem Copyright-Datum des Mannes seit mindestens 2006 verfügbar ist. Also habe ich eine Abwertung vorgenommen (- 1), nichts Persönliches, nur die feste Überzeugung, dass die Verwendung der von den Kernel-Entwicklern bereitgestellten Kernel-implementierten Tools korrekt ist.
Craig Hicks
42

Es gibt einen Wrapper um den Systemaufruf Flock (2), der einfallslos Flock (1) genannt wird. Dies macht es relativ einfach, exklusive Sperren zuverlässig zu erhalten, ohne sich um Bereinigung usw. kümmern zu müssen. Auf der Manpage finden Sie Beispiele für die Verwendung in einem Shell-Skript.

Cowan
quelle
3
Der flock()Systemaufruf ist nicht POSIX und funktioniert nicht für Dateien auf NFS-Mounts.
Maxschlepzig
17
Ich verwende einen Cron-Job flock -x -n %lock file% -c "%command%", um sicherzustellen, dass immer nur eine Instanz ausgeführt wird.
Ryall
Aww, anstelle der einfallslosen Herde (1) hätten sie sich für so etwas wie eine Herde (U) entscheiden sollen. .. .es hat eine gewisse Vertrautheit damit. . Es scheint, als hätte ich das schon vor ein oder zwei Jahren gehört.
Kent Kruckeberg
Es ist bemerkenswert, dass die Dokumentation zu Flock (2) die Verwendung nur für Dateien angibt, während die Dokumentation zu Flock (1) die Verwendung für Dateien oder Verzeichnisse angibt. In der Dokumentation zu Flock (1) wird nicht explizit angegeben, wie der Unterschied während der Erstellung angegeben werden soll, aber ich gehe davon aus, dass dies durch Hinzufügen eines abschließenden "/" erfolgt. Wenn Flock (1) Verzeichnisse verarbeiten kann, Flock (2) jedoch nicht, wird Flock (1) nicht nur bei Flock (2) implementiert.
Craig Hicks
27

Sie benötigen eine atomare Operation wie Herde, sonst schlägt dies schließlich fehl.

Aber was tun, wenn keine Herde verfügbar ist? Nun, da ist mkdir. Das ist auch eine atomare Operation. Nur ein Prozess führt zu einem erfolgreichen mkdir, alle anderen schlagen fehl.

Der Code lautet also:

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

Sie müssen sich um veraltete Sperren kümmern, sonst wird Ihr Skript nach einem Absturz nie wieder ausgeführt.

Gunstick
quelle
1
Führen Sie dies einige Male gleichzeitig aus (z. B. "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh.) & ") und das Skript wird einige Male durchgesickert.
Nippysaurus
7
@Nippysaurus: Diese Sperrmethode leckt nicht. Was Sie gesehen haben, war das anfängliche Skript, das vor dem Start aller Kopien beendet wurde, sodass ein anderes Skript die Sperre (korrekt) erhalten konnte. Um dieses falsche Positiv zu vermeiden, fügen Sie ein sleep 10Vorher hinzu rmdirund versuchen Sie erneut, eine Kaskade zu erstellen - nichts "leckt".
Sir Athos
Andere Quellen behaupten, mkdir sei auf einigen Dateisystemen wie NFS nicht atomar. Übrigens habe ich Gelegenheiten gesehen, in denen bei NFS gleichzeitig rekursives mkdir manchmal zu Fehlern bei Jenkins-Matrix-Jobs führt. Ich bin mir also ziemlich sicher, dass dies der Fall ist. Aber mkdir ist ziemlich nett für weniger anspruchsvolle Anwendungsfälle IMO.
Akostadinov
Sie können die Bash'es Noclobber-Option mit regulären Dateien verwenden.
Palec
26

Um die Verriegelung zuverlässig zu machen, benötigen Sie eine atomare Operation. Viele der oben genannten Vorschläge sind nicht atomar. Das vorgeschlagene Dienstprogramm lockfile (1) sieht vielversprechend aus, da auf der Manpage erwähnt wird, dass es "NFS-resistent" ist. Wenn Ihr Betriebssystem Lockfile (1) nicht unterstützt und Ihre Lösung auf NFS funktionieren muss, haben Sie nicht viele Optionen.

NFSv2 hat zwei atomare Operationen:

  • symlink
  • umbenennen

Mit NFSv3 ist der Erstellungsaufruf auch atomar.

Verzeichnisoperationen sind unter NFSv2 und NFSv3 NICHT atomar (siehe das Buch 'NFS Illustrated' von Brent Callaghan, ISBN 0-201-32570-5; Brent ist ein NFS-Veteran bei Sun).

Wenn Sie dies wissen, können Sie Spin-Locks für Dateien und Verzeichnisse implementieren (in der Shell, nicht in PHP):

Stromverzeichnis sperren:

while ! ln -s . lock; do :; done

Datei sperren:

while ! ln -s ${f} ${f}.lock; do :; done

aktuelles Verzeichnis entsperren (Annahme, der laufende Prozess hat die Sperre wirklich erhalten):

mv lock deleteme && rm deleteme

Entsperren einer Datei (Annahme, der laufende Prozess hat die Sperre wirklich erhalten):

mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme

Entfernen ist auch nicht atomar, daher zuerst das Umbenennen (das atomar ist) und dann das Entfernen.

Für die Symlink- und Umbenennungsaufrufe müssen sich beide Dateinamen im selben Dateisystem befinden. Mein Vorschlag: Verwenden Sie nur einfache Dateinamen (keine Pfade) und legen Sie Datei und Sperre in dasselbe Verzeichnis.


quelle
Welche Seiten von NFS Illustrated unterstützen die Aussage, dass mkdir gegenüber NFS nicht atomar ist?
Maxschlepzig
Danke für diese Technik. Eine Shell-Mutex-Implementierung ist in meiner neuen Shell-Bibliothek verfügbar: github.com/Offirmo/offirmo-shell-lib , siehe "Mutex". Es wird verwendet, lockfilefalls verfügbar, oder auf diese symlinkMethode zurückgreifen, wenn nicht.
Offirmo
Nett. Leider bietet diese Methode keine Möglichkeit, veraltete Sperren automatisch zu löschen.
Richard Hansen
Sollte für die zweistufige Freischaltung ( mv, rm) rm -fverwendet werden, anstatt rmfür den Fall, dass zwei Prozesse P1, P2 laufen? Zum Beispiel beginnt P1 mit dem Entsperren mv, dann sperrt P2, dann entsperrt P2 (beide mvund rm), schließlich versucht P1 rmund schlägt fehl.
Matt Wallis
1
@MattWallis Dieses letzte Problem kann leicht durch Einfügen $$in den ${f}.deletemeDateinamen behoben werden .
Stefan Majewsky
23

Eine andere Option besteht darin, die Shell- noclobberOption durch Ausführen zu verwenden set -C. Dann >schlägt fehl, wenn die Datei bereits vorhanden ist.

In Kürze:

set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
    echo "Successfully acquired lock"
    # do work
    rm "$lockfile"    # XXX or via trap - see below
else
    echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi

Dies führt dazu, dass die Shell Folgendes aufruft:

open(pathname, O_CREAT|O_EXCL)

Dies erstellt die Datei atomar oder schlägt fehl, wenn die Datei bereits vorhanden ist.


Laut einem Kommentar zu BashFAQ 045 kann dies fehlschlagen ksh88, aber es funktioniert in allen meinen Shells:

$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3

$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3

Interessant, dass pdkshdas O_TRUNCFlag hinzugefügt wird, aber offensichtlich überflüssig ist:
Entweder erstellen Sie eine leere Datei oder Sie tun nichts.


Wie Sie dies tun, rmhängt davon ab, wie unreine Exits behandelt werden sollen.

Bei sauberem Ausgang löschen

Neue Läufe schlagen fehl, bis das Problem, durch das der unsaubere Exit behoben wurde, behoben und die Sperrdatei manuell entfernt wurde.

# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"

Bei jedem Ausgang löschen

Neue Läufe sind erfolgreich, sofern das Skript noch nicht ausgeführt wird.

trap 'rm "$lockfile"' EXIT
Mikel
quelle
Sehr neuartiger Ansatz ... dies scheint eine Möglichkeit zu sein, die Atomizität mithilfe einer Sperrdatei anstelle eines Sperrverzeichnisses zu erreichen.
Matt Caldwell
Netter Ansatz. :-) In der EXIT-Trap sollte eingeschränkt werden, welcher Prozess die Sperrdatei bereinigen kann. Zum Beispiel: trap 'if [[$ (cat "$ lockfile") == "$$"]]; dann rm "$ lockfile"; fi 'EXIT
Kevin Seifert
1
Sperrdateien sind über NFS nicht atomar. Aus diesem Grund haben die Leute Sperrverzeichnisse verwendet.
K Richard Pixley
20

Sie können GNU Paralleldies verwenden, da es als Mutex funktioniert, wenn es als aufgerufen wird sem. Konkret können Sie also Folgendes verwenden:

sem --id SCRIPTSINGLETON yourScript

Wenn Sie auch eine Zeitüberschreitung wünschen, verwenden Sie:

sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript

Timeout von <0 bedeutet Beenden ohne Ausführen des Skripts, wenn das Semaphor nicht innerhalb des Timeouts freigegeben wird. Timeout von> 0 bedeutet, dass das Skript trotzdem ausgeführt wird.

Beachten Sie, dass Sie ihm einen Namen (mit --id) geben sollten, sonst wird standardmäßig das steuernde Terminal verwendet.

GNU Parallel ist eine sehr einfache Installation auf den meisten Linux / OSX / Unix-Plattformen - es ist nur ein Perl-Skript.

Mark Setchell
quelle
Schade, dass die Leute nur ungern nutzlose Antworten ablehnen: Dies führt dazu, dass neue relevante Antworten in einem Haufen Müll vergraben werden.
Dmitry Grigoryev
4
Wir brauchen nur viele positive Stimmen. Dies ist eine so ordentliche und wenig bekannte Antwort. (Um pedantisch zu sein, wollte OP schnell und schmutzig sein, während dies schnell und sauber ist!) Mehr dazu semunter verwandter Frage unix.stackexchange.com/a/322200/199525 .
Teilweise bewölkt
16

Bei Shell-Skripten tendiere ich dazu, das mkdirOver zu verwenden, flockda dadurch die Sperren portabler werden.

In jedem Fall reicht die Verwendung set -enicht aus. Das Skript wird nur beendet, wenn ein Befehl fehlschlägt. Ihre Schlösser bleiben weiterhin zurück.

Für eine ordnungsgemäße Bereinigung der Sperren sollten Sie Ihre Traps wirklich auf so etwas wie diesen Pseudocode setzen (aufgehoben, vereinfacht und ungetestet, aber von aktiv verwendeten Skripten):

#=======================================================================
# Predefined Global Variables
#=======================================================================

TMPDIR=/tmp/myapp
[[ ! -d $TMP_DIR ]] \
    && mkdir -p $TMP_DIR \
    && chmod 700 $TMPDIR

LOCK_DIR=$TMP_DIR/lock

#=======================================================================
# Functions
#=======================================================================

function mklock {
    __lockdir="$LOCK_DIR/$(date +%s.%N).$$" # Private Global. Use Epoch.Nano.PID

    # If it can create $LOCK_DIR then no other instance is running
    if $(mkdir $LOCK_DIR)
    then
        mkdir $__lockdir  # create this instance's specific lock in queue
        LOCK_EXISTS=true  # Global
    else
        echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
        exit 1001  # Or work out some sleep_while_execution_lock elsewhere
    fi
}

function rmlock {
    [[ ! -d $__lockdir ]] \
        && echo "WARNING: Lock is missing. $__lockdir does not exist" \
        || rmdir $__lockdir
}

#-----------------------------------------------------------------------
# Private Signal Traps Functions {{{2
#
# DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or 
#         there will be *NO CLEAN UP*. You'll have to manually remove 
#         any locks in place.
#-----------------------------------------------------------------------
function __sig_exit {

    # Place your clean up logic here 

    # Remove the LOCK
    [[ -n $LOCK_EXISTS ]] && rmlock
}

function __sig_int {
    echo "WARNING: SIGINT caught"    
    exit 1002
}

function __sig_quit {
    echo "SIGQUIT caught"
    exit 1003
}

function __sig_term {
    echo "WARNING: SIGTERM caught"    
    exit 1015
}

#=======================================================================
# Main
#=======================================================================

# Set TRAPs
trap __sig_exit EXIT    # SIGEXIT
trap __sig_int INT      # SIGINT
trap __sig_quit QUIT    # SIGQUIT
trap __sig_term TERM    # SIGTERM

mklock

# CODE

exit # No need for cleanup code here being in the __sig_exit trap function

Folgendes wird passieren. Alle Fallen erzeugen einen Ausgang, so dass die Funktion __sig_exitimmer ausgeführt wird (mit Ausnahme eines SIGKILL), der Ihre Schlösser aufräumt.

Hinweis: Meine Exit-Werte sind keine niedrigen Werte. Warum? Verschiedene Stapelverarbeitungssysteme stellen die Erwartungen 0 bis 31 her oder erwarten sie. Wenn ich sie auf etwas anderes setze, kann ich meine Skripte und Stapelströme entsprechend auf den vorherigen Stapeljob oder das vorherige Skript reagieren lassen.

Mark Stinson
quelle
2
Ihr Skript ist viel zu ausführlich, hätte meiner Meinung nach viel kürzer sein können, aber insgesamt müssen Sie Traps einrichten, um dies richtig zu machen. Außerdem würde ich SIGHUP hinzufügen.
Mojuba
Dies funktioniert gut, außer dass nach $ LOCK_DIR gesucht wird, während $ __ lockdir entfernt wird. Vielleicht sollte ich vorschlagen, wenn Sie das Schloss entfernen, würden Sie rm -r $ LOCK_DIR tun?
Bevada
Vielen Dank für den Vorschlag. Der oben genannte Code wurde angehoben und in Pseudo-Code-Form platziert, sodass eine Anpassung basierend auf der Verwendung durch die Benutzer erforderlich ist. In meinem Fall habe ich mich jedoch bewusst für rmdir entschieden, da rmdir Verzeichnisse nur dann sicher entfernt, wenn sie leer sind. Wenn Leute Ressourcen wie PID-Dateien usw. in sie einfügen, sollten sie ihre Sperrbereinigung aggressiver ändern rm -r $LOCK_DIRoder sie sogar nach Bedarf erzwingen (wie ich es auch in besonderen Fällen getan habe, z. B. wenn relative Scratch-Dateien gespeichert sind). Prost.
Mark Stinson
Hast du getestet exit 1002?
Gilles Quenot
13

Wirklich schnell und wirklich dreckig? Dieser Einzeiler oben in Ihrem Skript funktioniert:

[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit

Stellen Sie natürlich sicher, dass Ihr Skriptname eindeutig ist. :) :)

Majal
quelle
Wie simuliere ich das, um es zu testen? Gibt es eine Möglichkeit, ein Skript zweimal in einer Zeile zu starten und möglicherweise eine Warnung zu erhalten, wenn es bereits ausgeführt wird?
rubo77
2
Das funktioniert überhaupt nicht! Warum überprüfen -gt 2? grep befindet sich nicht immer im Ergebnis von ps!
rubo77
pgrepist nicht in POSIX. Wenn Sie dies portabel zum Laufen bringen möchten, benötigen Sie POSIX psund verarbeiten die Ausgabe.
Palec
Unter OSX -cexistiert nicht, müssen Sie verwenden | wc -l. Über den Zahlenvergleich: -gt 1wird geprüft, da sich die erste Instanz selbst sieht.
Benjamin Peter
6

Hier ist ein Ansatz, der das Sperren atomarer Verzeichnisse mit einer Überprüfung auf veraltete Sperre über PID kombiniert und neu startet, wenn veraltet. Auch dies beruht nicht auf irgendwelchen Bashismen.

#!/bin/dash

SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"

if ! mkdir $LOCKDIR 2>/dev/null
then
    # lock failed, but check for stale one by checking if the PID is really existing
    PID=$(cat $PIDFILE)
    if ! kill -0 $PID 2>/dev/null
    then
       echo "Removing stale lock of nonexistent PID ${PID}" >&2
       rm -rf $LOCKDIR
       echo "Restarting myself (${SCRIPTNAME})" >&2
       exec "$0" "$@"
    fi
    echo "$SCRIPTNAME is already running, bailing out" >&2
    exit 1
else
    # lock successfully acquired, save PID
    echo $$ > $PIDFILE
fi

trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT


echo hello

sleep 30s

echo bye
bk138
quelle
5

Erstellen Sie eine Sperrdatei an einem bekannten Speicherort und überprüfen Sie beim Start des Skripts, ob sie vorhanden ist. Das Einfügen der PID in die Datei kann hilfreich sein, wenn jemand versucht, eine fehlerhafte Instanz aufzuspüren, die die Ausführung des Skripts verhindert.

rauben
quelle
5

Dieses Beispiel wird in der Menschenherde erklärt, aber es bedarf einiger Verbesserungen, da wir Fehler und Exit-Codes verwalten sollten:

   #!/bin/bash
   #set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.

( #start subprocess
  # Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
  flock -x -w 10 200
  if [ "$?" != "0" ]; then echo Cannot lock!; exit 1; fi
  echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom  ) 200>/var/lock/.myscript.exclusivelock.
  # Do stuff
  # you can properly manage exit codes with multiple command and process algorithm.
  # I suggest throw this all to external procedure than can properly handle exit X commands

) 200>/var/lock/.myscript.exclusivelock   #exit subprocess

FLOCKEXIT=$?  #save exitcode status
    #do some finish commands

exit $FLOCKEXIT   #return properly exitcode, may be usefull inside external scripts

Sie können eine andere Methode verwenden, um Prozesse aufzulisten, die ich in der Vergangenheit verwendet habe. Dies ist jedoch komplizierter als die oben beschriebene Methode. Sie sollten Prozesse nach ps auflisten, nach ihrem Namen filtern, zusätzlichen Filter grep -v grep zum Entfernen von Parasiten und schließlich nach grep -c zählen. und mit der Zahl vergleichen. Es ist kompliziert und unsicher

Znik
quelle
1
Sie können ln -s verwenden, da dies nur dann einen Symlink erstellen kann, wenn keine Datei oder kein Symlink vorhanden ist, genau wie bei mkdir. Viele Systemprozesse haben in der Vergangenheit Symlinks verwendet, zum Beispiel init oder inetd. synlink behält die Prozess-ID bei, zeigt aber wirklich auf nichts. für die Jahre wurde dieses Verhalten geändert. Prozesse verwenden Herden und Semaphoren.
Znik
5

Die vorhandenen Antworten basieren entweder auf dem CLI-Dienstprogramm flockoder sichern die Sperrdatei nicht ordnungsgemäß. Das Flock-Dienstprogramm ist nicht auf allen Nicht-Linux-Systemen (z. B. FreeBSD) verfügbar und funktioniert unter NFS nicht ordnungsgemäß.

In meinen frühen Tagen der Systemadministration und Systementwicklung wurde mir gesagt, dass eine sichere und relativ tragbare Methode zum Erstellen einer Sperrdatei darin besteht, eine temporäre Datei mit mkemp(3)oder zu erstellen, indem mkemp(1)identifizierende Informationen in die temporäre Datei (dh PID) geschrieben und dann fest verknüpft werden die temporäre Datei zur Sperrdatei. Wenn die Verknüpfung erfolgreich war, haben Sie die Sperre erfolgreich erhalten.

Wenn ich Sperren in Shell-Skripten verwende, platziere ich normalerweise eine obtain_lock()Funktion in einem freigegebenen Profil und beziehe sie dann aus den Skripten. Unten ist ein Beispiel für meine Sperrfunktion:

obtain_lock()
{
  LOCK="${1}"
  LOCKDIR="$(dirname "${LOCK}")"
  LOCKFILE="$(basename "${LOCK}")"

  # create temp lock file
  TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
  if test "x${TMPLOCK}" == "x";then
     echo "unable to create temporary file with mktemp" 1>&2
     return 1
  fi
  echo "$$" > "${TMPLOCK}"

  # attempt to obtain lock file
  ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
  if test $? -ne 0;then
     rm -f "${TMPLOCK}"
     echo "unable to obtain lockfile" 1>&2
     if test -f "${LOCK}";then
        echo "current lock information held by: $(cat "${LOCK}")" 1>&2
     fi
     return 2
  fi
  rm -f "${TMPLOCK}"

  return 0;
};

Das folgende Beispiel zeigt, wie die Sperrfunktion verwendet wird:

#!/bin/sh

. /path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"

clean_up()
{
  rm -f "${PROG_LOCKFILE}"
}

obtain_lock "${PROG_LOCKFILE}"
if test $? -ne 0;then
   exit 1
fi
trap clean_up SIGHUP SIGINT SIGTERM

# bulk of script

clean_up
exit 0
# end of script

Denken Sie daran, clean_upan beliebigen Ausstiegspunkten in Ihrem Skript aufzurufen .

Ich habe das oben Genannte sowohl in Linux- als auch in FreeBSD-Umgebungen verwendet.

David M. Syzdek
quelle
4

Wenn ich auf eine Debian-Maschine ziele, finde ich das lockfile-progsPaket eine gute Lösung. procmailkommt auch mit einem lockfileWerkzeug. Manchmal stecke ich jedoch mit keinem von beiden fest.

Hier ist meine Lösung, die mkdirAtomic-Ness und eine PID-Datei verwendet, um veraltete Sperren zu erkennen. Dieser Code wird derzeit in einem Cygwin-Setup produziert und funktioniert gut.

Um es zu benutzen, rufen Sie einfach an, exclusive_lock_requirewenn Sie exklusiven Zugriff auf etwas benötigen. Mit einem optionalen Parameter für den Sperrenamen können Sie Sperren für verschiedene Skripts freigeben. Es gibt auch zwei Funktionen auf niedrigerer Ebene ( exclusive_lock_tryund exclusive_lock_retry), falls Sie etwas Komplexeres benötigen.

function exclusive_lock_try() # [lockname]
{

    local LOCK_NAME="${1:-`basename $0`}"

    LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
    local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"

    if [ -e "$LOCK_DIR" ]
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
        then
            # locked by non-dead process
            echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
            return 1
        else
            # orphaned lock, take it over
            ( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
        fi
    fi
    if [ "`trap -p EXIT`" != "" ]
    then
        # already have an EXIT trap
        echo "Cannot get lock, already have an EXIT trap"
        return 1
    fi
    if [ "$LOCK_PID" != "$$" ] &&
        ! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
    then
        local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
        # unable to acquire lock, new process got in first
        echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
        return 1
    fi
    trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT

    return 0 # got lock

}

function exclusive_lock_retry() # [lockname] [retries] [delay]
{

    local LOCK_NAME="$1"
    local MAX_TRIES="${2:-5}"
    local DELAY="${3:-2}"

    local TRIES=0
    local LOCK_RETVAL

    while [ "$TRIES" -lt "$MAX_TRIES" ]
    do

        if [ "$TRIES" -gt 0 ]
        then
            sleep "$DELAY"
        fi
        local TRIES=$(( $TRIES + 1 ))

        if [ "$TRIES" -lt "$MAX_TRIES" ]
        then
            exclusive_lock_try "$LOCK_NAME" > /dev/null
        else
            exclusive_lock_try "$LOCK_NAME"
        fi
        LOCK_RETVAL="${PIPESTATUS[0]}"

        if [ "$LOCK_RETVAL" -eq 0 ]
        then
            return 0
        fi

    done

    return "$LOCK_RETVAL"

}

function exclusive_lock_require() # [lockname] [retries] [delay]
{
    if ! exclusive_lock_retry "$@"
    then
        exit 1
    fi
}
Jason verwittert
quelle
Danke, habe es selbst auf Cygwin versucht und es hat einfache Tests bestanden.
Ndemou
4

Wenn die Einschränkungen von Flock, die bereits an anderer Stelle in diesem Thread beschrieben wurden, für Sie kein Problem darstellen, sollte dies funktionieren:

#!/bin/bash

{
    # exit if we are unable to obtain a lock; this would happen if 
    # the script is already running elsewhere
    # note: -x (exclusive) is the default
    flock -n 100 || exit

    # put commands to run here
    sleep 100
} 100>/tmp/myjob.lock 
presto8
quelle
3
Ich dachte nur, ich möchte darauf hinweisen, dass -x (Schreibsperre) bereits standardmäßig eingestellt ist.
Keldon Alleyne
und -nwird exit 1sofort, wenn es das Schloss nicht bekommen kann
Anentropic
Danke @KeldonAlleyne, ich habe den Code aktualisiert, um "-x" zu entfernen, da er Standard ist.
Presto8
3

Einige Unixe haben, lockfiledie den bereits erwähnten sehr ähnlich sind flock.

Aus der Manpage:

Mit lockfile können eine oder mehrere Semaphor-Dateien erstellt werden. Wenn die Sperrdatei nicht alle angegebenen Dateien (in der angegebenen Reihenfolge) erstellen kann, wartet sie auf die Schlafzeit (standardmäßig 8 Sekunden) und wiederholt die letzte Datei, die nicht erfolgreich war. Sie können die Anzahl der Wiederholungen angeben, die ausgeführt werden sollen, bis ein Fehler zurückgegeben wird. Wenn die Anzahl der Wiederholungsversuche -1 ist (Standard, dh -r-1), wird die Sperrdatei für immer wiederholt.

dmckee --- Ex-Moderator Kätzchen
quelle
Wie bekommen wir das lockfileDienstprogramm?
Offirmo
lockfilewird verteilt mit procmail. Es gibt auch eine Alternative dotlockfile, die zum liblockfilePaket gehört. Beide behaupten, zuverlässig an NFS zu arbeiten.
Mr. Deathless
3

Obwohl die Antwort von bmdhacks fast gut ist, besteht eine geringe Wahrscheinlichkeit, dass das zweite Skript ausgeführt wird, nachdem die Sperrdatei zuerst überprüft und bevor sie geschrieben wurde. Also schreiben beide die Sperrdatei und sie werden beide ausgeführt. So funktioniert es sicher:

lockfile=/var/lock/myscript.lock

if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
  trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
  # or you can decide to skip the "else" part if you want
  echo "Another instance is already running!"
fi

Die noclobberOption stellt sicher, dass der Umleitungsbefehl fehlschlägt, wenn die Datei bereits vorhanden ist. Der Umleitungsbefehl ist also tatsächlich atomar - Sie schreiben und überprüfen die Datei mit einem Befehl. Sie müssen die Sperrdatei am Ende der Datei nicht entfernen - sie wird von der Falle entfernt. Ich hoffe, das hilft Leuten, die es später lesen werden.

PS Ich habe nicht gesehen, dass Mikel die Frage bereits richtig beantwortet hat, obwohl er den Befehl trap nicht eingefügt hat, um die Wahrscheinlichkeit zu verringern, dass die Sperrdatei übrig bleibt, nachdem das Skript beispielsweise mit Strg-C gestoppt wurde. Das ist also die komplette Lösung

NickSoft
quelle
3

Ich wollte auf Sperrdateien, Sperrverzeichnisse, spezielle Sperrprogramme verzichten und auch pidofnicht auf alle Linux-Installationen. Wollte auch den einfachsten Code haben (oder mindestens so wenige Zeilen wie möglich). Einfachste ifAussage in einer Zeile:

if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
linux_newbie
quelle
1
Dies ist abhängig von der Ausgabe von 'ps' auf meinem Computer (Ubuntu 14.04, / bin / ps aus procps-ng Version 3.3.9). Der Befehl 'ps axf' druckt ASCII-Baumzeichen, die die Feldnummern stören. Dies funktionierte für mich: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Qneill
2

Ich verwende einen einfachen Ansatz, der veraltete Sperrdateien behandelt.

Beachten Sie, dass einige der oben genannten Lösungen, in denen die PID gespeichert ist, die Tatsache ignorieren, dass die PID umlaufen kann. Es reicht also nicht aus, nur zu überprüfen, ob ein gültiger Prozess mit der gespeicherten PID vorliegt, insbesondere bei Skripten mit langer Laufzeit.

Ich verwende noclobber, um sicherzustellen, dass nur ein Skript gleichzeitig geöffnet und in die Sperrdatei geschrieben werden kann. Außerdem speichere ich genügend Informationen, um einen Prozess in der Sperrdatei eindeutig zu identifizieren. Ich definiere den Datensatz, um einen Prozess eindeutig zu identifizieren, der pid, ppid, lstart sein soll.

Wenn ein neues Skript gestartet wird und die Sperrdatei nicht erstellt werden kann, wird überprüft, ob der Prozess, der die Sperrdatei erstellt hat, noch vorhanden ist. Wenn nicht, gehen wir davon aus, dass der ursprüngliche Prozess eines unanständigen Todes gestorben ist und eine veraltete Sperrdatei hinterlassen hat. Das neue Skript übernimmt dann den Besitz der Sperrdatei, und alles ist wieder in Ordnung.

Sollte mit mehreren Shells auf mehreren Plattformen funktionieren. Schnell, tragbar und einfach.

#!/usr/bin/env sh
# Author: rouble

LOCKFILE=/var/tmp/lockfile #customize this line

trap release INT TERM EXIT

# Creates a lockfile. Sets global variable $ACQUIRED to true on success.
# 
# Returns 0 if it is successfully able to create lockfile.
acquire () {
    set -C #Shell noclobber option. If file exists, > will fail.
    UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
    if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
        ACQUIRED="TRUE"
        return 0
    else
        if [ -e $LOCKFILE ]; then 
            # We may be dealing with a stale lock file.
            # Bring out the magnifying glass. 
            CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
            CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
            CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
            if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then 
                echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
                return 1
            else
                # The process that created this lock file died an ungraceful death. 
                # Take ownership of the lock file.
                echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
                release "FORCE"
                if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
                    ACQUIRED="TRUE"
                    return 0
                else
                    echo "Cannot write to $LOCKFILE. Error." >&2
                    return 1
                fi
            fi
        else
            echo "Do you have write permissons to $LOCKFILE ?" >&2
            return 1
        fi
    fi
}

# Removes the lock file only if this script created it ($ACQUIRED is set), 
# OR, if we are removing a stale lock file (first parameter is "FORCE") 
release () {
    #Destroy lock file. Take no prisoners.
    if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
        rm -f $LOCKFILE
    fi
}

# Test code
# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if [ $? -eq 0 ]; then 
    echo "Acquired lock."
    read -p "Press [Enter] key to release lock..."
    release
    echo "Released lock."
else
    echo "Unable to acquire lock."
fi
Rubel
quelle
Ich habe dir +1 für eine andere Lösung gegeben. Obwohl es auch in AIX nicht funktioniert (> ps -eo pid, ppid, lstart $$ | tail -1 ps: ungültige Liste mit -o.), Nicht in HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: illegale Option - o). Vielen Dank.
Tagar
2

Fügen Sie diese Zeile am Anfang Ihres Skripts hinzu

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :

Es ist ein Boilerplate-Code von Man Herde.

Wenn Sie mehr Protokollierung wünschen, verwenden Sie diese

[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."

Dies setzt und überprüft Sperren mit flock Dienstprogramms festgelegt . Dieser Code erkennt, ob er zum ersten Mal ausgeführt wurde, indem er die FLOCKER-Variable überprüft. Wenn er nicht auf den Skriptnamen festgelegt ist, versucht er, das Skript rekursiv mit flock erneut zu starten. Wenn die FLOCKER-Variable initialisiert ist, wird FLOCKER bei der vorherigen Iteration angeflockt erfolgreich und es ist in Ordnung fortzufahren. Wenn die Sperre besetzt ist, schlägt sie mit dem konfigurierbaren Exit-Code fehl.

Es scheint unter Debian 7 nicht zu funktionieren, scheint aber mit dem experimentellen Util-Linux 2.25-Paket wieder zu funktionieren. Es schreibt "Herde: ... Textdatei beschäftigt". Es kann überschrieben werden, indem die Schreibberechtigung für Ihr Skript deaktiviert wird.

user3132194
quelle
1

PID und Lockfiles sind definitiv die zuverlässigsten. Wenn Sie versuchen, das Programm auszuführen, kann es nach der Sperrdatei suchen, die vorhanden ist, und prüfen ps, ob der Prozess noch ausgeführt wird. Ist dies nicht der Fall, kann das Skript gestartet werden und die PID in der Sperrdatei auf ihre eigene aktualisieren.

Drew Stephens
quelle
1

Ich finde, dass die Lösung von bmdhack die praktischste ist, zumindest für meinen Anwendungsfall. Die Verwendung von Flock und Lockfile setzt voraus, dass die Lockfile beim Beenden des Skripts mit rm entfernt wird, was nicht immer garantiert werden kann (z. B. kill -9).

Ich würde eine Kleinigkeit an der Lösung von bmdhack ändern: Es geht darum, die Sperrdatei zu entfernen, ohne anzugeben, dass dies für das sichere Funktionieren dieses Semaphors nicht erforderlich ist. Seine Verwendung von kill -0 stellt sicher, dass eine alte Sperrdatei für einen toten Prozess einfach ignoriert / überschrieben wird.

Meine vereinfachte Lösung besteht daher darin, einfach Folgendes oben in Ihren Singleton einzufügen:

## Test the lock
LOCKFILE=/tmp/singleton.lock 
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
    echo "Script already running. bye!"
    exit 
fi

## Set the lock 
echo $$ > ${LOCKFILE}

Natürlich hat dieses Skript immer noch den Fehler, dass Prozesse, die wahrscheinlich zur gleichen Zeit starten, eine Renngefahr darstellen, da der Sperrtest und die Set-Operationen keine einzelne atomare Aktion sind. Die von lhunath vorgeschlagene Lösung für die Verwendung von mkdir hat jedoch den Fehler, dass ein getötetes Skript das Verzeichnis zurücklässt und somit die Ausführung anderer Instanzen verhindert.

thecowster
quelle
1

Die semaphoric Dienstprogramm verwendet flock(wie oben, beispielsweise durch presto8 erörtert) , um eine zu implementieren Zählen Semaphor . Es ermöglicht eine beliebige Anzahl von gleichzeitigen Prozessen, die Sie möchten. Wir verwenden es, um den Grad der Parallelität verschiedener Warteschlangen-Worker-Prozesse zu begrenzen.

Es ist wie Sem, aber viel leichter. (Vollständige Offenlegung: Ich habe es geschrieben, nachdem ich festgestellt hatte, dass das Sem viel zu schwer für unsere Bedürfnisse war und es kein einfaches Dienstprogramm zum Zählen von Semaphoren gab.)

Tim Bunce
quelle
1

Ein Beispiel mit Herde (1), aber ohne Unterschale. flock () ed Datei / tmp / foo wird nie entfernt, aber das spielt keine Rolle, da es flock () und un-flock () ed bekommt.

#!/bin/bash

exec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; then
    echo "lock failed, exiting"
    exit
fi

#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&- #close fd 9, and release lock

#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5
Sivann
quelle
1

Bereits millionenfach beantwortet, aber auf andere Weise, ohne dass externe Abhängigkeiten erforderlich sind:

LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/`cat $LOCK_FILE` ]]; then
   // Process already exists
   exit 1
fi
echo $$ > $LOCK_FILE

Jedes Mal, wenn die aktuelle PID ($$) in die Sperrdatei geschrieben wird und beim Start des Skripts überprüft wird, ob ein Prozess mit der neuesten PID ausgeführt wird.

Filidor Wiese
quelle
1
Ohne den Trap-Aufruf (oder zumindest eine Bereinigung gegen Ende für den Normalfall) haben Sie den falsch positiven Fehler, bei dem die Sperrdatei nach dem letzten Lauf verbleibt und die PID später von einem anderen Prozess wiederverwendet wurde. (Und im schlimmsten Fall wurde es einem langen Prozess wie Apache geschenkt ...)
Philippe Chaintreuil
1
Ich stimme zu, mein Ansatz ist fehlerhaft, es braucht eine Falle. Ich habe meine Lösung aktualisiert. Ich bevorzuge immer noch keine externen Abhängigkeiten.
Filidor Wiese
1

Die Verwendung der Prozesssperre ist viel stärker und sorgt auch für die unansehnlichen Ausgänge. lock_file bleibt geöffnet, solange der Prozess ausgeführt wird. Es wird (per Shell) geschlossen, sobald der Prozess existiert (auch wenn er beendet wird). Ich fand das sehr effizient:

lock_file=/tmp/`basename $0`.lock

if fuser $lock_file > /dev/null 2>&1; then
    echo "WARNING: Other instance of $(basename $0) running."
    exit 1
fi
exec 3> $lock_file 
Sudhir Kumar
quelle
1

Ich benutze oneliner @ ganz am Anfang des Skripts:

#!/bin/bash

if [[ $(pgrep -afc "$(basename "$0")") -gt "1" ]]; then echo "Another instance of "$0" has already been started!" && exit; fi
.
the_beginning_of_actual_script

Es ist gut, das Vorhandensein eines Prozesses im Speicher zu sehen (unabhängig vom Status des Prozesses). aber es macht den Job für mich.

Z KC
quelle
0

Der Herdenweg ist der richtige Weg. Überlegen Sie, was passiert, wenn das Skript plötzlich stirbt. In der Herde verlieren Sie nur die Herde, aber das ist kein Problem. Beachten Sie auch, dass ein böser Trick darin besteht, eine Herde am Skript selbst zu haben. Aber damit können Sie natürlich mit voller Kraft auf Berechtigungsprobleme stoßen.

Ich gebe schlechte Antworten
quelle
0

Schnell und dreckig?

#!/bin/sh

if [ -f sometempfile ]
  echo "Already running... will now terminate."
  exit
else
  touch sometempfile
fi

..do what you want here..

rm sometempfile
Aupajo
quelle
7
Dies kann ein Problem sein oder auch nicht, je nachdem, wie es verwendet wird. Es gibt jedoch eine Race-Bedingung zwischen dem Testen der Sperre und dem Erstellen der Sperre, sodass zwei Skripte gleichzeitig gestartet werden können. Wenn einer zuerst beendet wird, läuft der andere ohne Sperrdatei weiter.
TimB
3
C News, in dem ich viel über tragbare Shell-Skripte gelernt habe, hat eine Lock. $$ -Datei erstellt und dann versucht, sie mit "lock" zu verknüpfen. Wenn die Verknüpfung erfolgreich war, hatten Sie die Sperre, andernfalls haben Sie die Sperre entfernt. $$ und verlassen.
Paul Tomblin
Das ist eine wirklich gute Möglichkeit, es sei denn, Sie müssen die Sperrdatei immer noch manuell entfernen, wenn etwas schief geht und die Sperrdatei nicht gelöscht wird.
Matthew Scharley
2
Schnell und dreckig, darum hat er gebeten :)
Aupajo