Antworten:
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 -0
der kein Signal liefert, sondern nur prüft, ob ein Prozess mit der angegebenen PID existiert. Durch den Aufruf von trap
wird auch sichergestellt, dass die Sperrdatei auch dann entfernt wird, wenn Ihr Prozess beendet wird (außer kill -9
).
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.
( command A ) command B
ruft 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.
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
Wenn 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?).
exit
ist aus dem Teil innerhalb der (
)
. Wenn der Unterprozess endet, wird die Sperre automatisch aufgehoben, da kein Prozess sie hält.
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
. mkdir
Erstellt 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) .
if ! mkdir
Teil 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
.
mkdir
nicht 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 mkdir
atomar implementiert wird .
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.
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 ).
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.
flock()
Systemaufruf ist nicht POSIX und funktioniert nicht für Dateien auf NFS-Mounts.
flock -x -n %lock file% -c "%command%"
, um sicherzustellen, dass immer nur eine Instanz ausgeführt wird.
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.
sleep 10
Vorher hinzu rmdir
und versuchen Sie erneut, eine Kaskade zu erstellen - nichts "leckt".
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:
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.
lockfile
falls verfügbar, oder auf diese symlink
Methode zurückgreifen, wenn nicht.
mv
, rm
) rm -f
verwendet werden, anstatt rm
für den Fall, dass zwei Prozesse P1, P2 laufen? Zum Beispiel beginnt P1 mit dem Entsperren mv
, dann sperrt P2, dann entsperrt P2 (beide mv
und rm
), schließlich versucht P1 rm
und schlägt fehl.
$$
in den ${f}.deleteme
Dateinamen behoben werden .
Eine andere Option besteht darin, die Shell- noclobber
Option 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 pdksh
das O_TRUNC
Flag hinzugefügt wird, aber offensichtlich überflüssig ist:
Entweder erstellen Sie eine leere Datei oder Sie tun nichts.
Wie Sie dies tun, rm
hä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
Sie können GNU Parallel
dies 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.
sem
unter verwandter Frage unix.stackexchange.com/a/322200/199525 .
Bei Shell-Skripten tendiere ich dazu, das mkdir
Over zu verwenden, flock
da dadurch die Sperren portabler werden.
In jedem Fall reicht die Verwendung set -e
nicht 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_exit
immer 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.
rm -r $LOCK_DIR
oder sie sogar nach Bedarf erzwingen (wie ich es auch in besonderen Fällen getan habe, z. B. wenn relative Scratch-Dateien gespeichert sind). Prost.
exit 1002
?
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. :) :)
-gt 2
? grep befindet sich nicht immer im Ergebnis von ps!
pgrep
ist nicht in POSIX. Wenn Sie dies portabel zum Laufen bringen möchten, benötigen Sie POSIX ps
und verarbeiten die Ausgabe.
-c
existiert nicht, müssen Sie verwenden | wc -l
. Über den Zahlenvergleich: -gt 1
wird geprüft, da sich die erste Instanz selbst sieht.
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
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
Die vorhandenen Antworten basieren entweder auf dem CLI-Dienstprogramm flock
oder 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_up
an beliebigen Ausstiegspunkten in Ihrem Skript aufzurufen .
Ich habe das oben Genannte sowohl in Linux- als auch in FreeBSD-Umgebungen verwendet.
Wenn ich auf eine Debian-Maschine ziele, finde ich das lockfile-progs
Paket eine gute Lösung. procmail
kommt auch mit einem lockfile
Werkzeug. Manchmal stecke ich jedoch mit keinem von beiden fest.
Hier ist meine Lösung, die mkdir
Atomic-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_require
wenn 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_try
und 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
}
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
-n
wird exit 1
sofort, wenn es das Schloss nicht bekommen kann
Einige Unixe haben, lockfile
die 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.
lockfile
Dienstprogramm?
lockfile
wird verteilt mit procmail
. Es gibt auch eine Alternative dotlockfile
, die zum liblockfile
Paket gehört. Beide behaupten, zuverlässig an NFS zu arbeiten.
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 noclobber
Option 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
Ich wollte auf Sperrdateien, Sperrverzeichnisse, spezielle Sperrprogramme verzichten und auch pidof
nicht auf alle Linux-Installationen. Wollte auch den einfachsten Code haben (oder mindestens so wenige Zeilen wie möglich). Einfachste if
Aussage in einer Zeile:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
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
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.
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.
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.
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.)
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
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.
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
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.
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.
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