Was ist eine schnelle und schmutzige Methode, um sicherzustellen, dass zu einem bestimmten Zeitpunkt nur eine Instanz eines Shell-Skripts ausgeführt wird?
Hier ist eine Implementierung, die eine lockfile verwendet und eine PID mit einbezieht. Dies dient als Schutz, wenn der Prozess vor dem Entfernen der pidfile 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 vorhanden ist. Durch den Aufruf von trap
wird auch sichergestellt, dass die lockfile entfernt wird, auch wenn Ihr Prozess beendet wird (außer kill -9
).
Verwenden Sie flock(1)
, um eine Exklusivbereichssperre zu einem Dateideskriptor 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
Dadurch wird sichergestellt, dass der Code zwischen (
und )
jeweils nur von einem Prozess ausgeführt wird und der Prozess nicht zu lange auf eine Sperre wartet.
Vorbehalt: Dieser Befehl ist Teil von util-linux
. Wenn Sie ein anderes Betriebssystem als Linux verwenden, ist es möglicherweise nicht verfügbar.
Alle Ansätze, die das Vorhandensein von Sperrdateien prüfen, sind fehlerhaft.
Warum? Es gibt keine Möglichkeit, zu überprüfen, ob eine Datei vorhanden ist, und sie in einer einzigen atomaren Aktion zu erstellen. Deswegen; Es gibt eine Rassenbedingung, dass WILL Ihre Versuche unternimmt, sich gegenseitig auszuschließen.
Stattdessen müssen Sie mkdir
verwenden. mkdir
erstellt ein Verzeichnis, falls es noch nicht vorhanden ist, und legt es einen Exit-Code fest. Noch wichtiger ist, dass dies alles in einer einzigen atomaren Aktion ausgeführt 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
Für alle Details sehen Sie die ausgezeichnete BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Wenn Sie auf veraltete Schlösser achten wollen, ist Fixierer (1) praktisch. Der einzige Nachteil hier ist, dass die Operation etwa eine Sekunde dauert und daher nicht sofort erfolgt.
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 wie folgt in einem Skript verwenden:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Wenn Sie sich nicht für die Portabilität interessieren (diese Lösungen sollten auf nahezu jeder UNIX-Box funktionieren), bietet Linux ' fuser (1) einige zusätzliche Optionen und es gibt auch flock (1) .
Es gibt einen Wrapper um den Flock (2) -Systemaufruf, der unvorstellbar Flock (1) genannt wird. Dies macht es relativ einfach, exklusive Sperren zuverlässig zu erhalten, ohne sich um Bereinigung usw. zu sorgen. Auf der Manpage finden Sie Beispiele zur Verwendung in einem Shell-Skript.
Sie brauchen eine atomare Operation, wie zum Beispiel eine Herde, sonst wird dies letztendlich fehlschlagen.
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 werden fehlschlagen.
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.
Um das Verriegeln 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 in der Manpage Erwähnt wird, dass es "NFS-resistent" ist. Wenn Ihr Betriebssystem die Sperrdatei (1) und Nicht unterstützt, muss Ihre Lösung mit NFS funktionieren. Sie haben nicht viele Optionen.
NFSv2 hat zwei atomare Operationen:
Bei NFSv3 ist der Aufruf zum Erstellen 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 Shell, nicht in PHP):
aktuelles Verzeichnis sperren:
while ! ln -s . lock; do :; done
eine Datei sperren:
while ! ln -s ${f} ${f}.lock; do :; done
aktuelles Verzeichnis entsperren (Annahme, der laufende Prozess hat tatsächlich die Sperre erhalten):
mv lock deleteme && rm deleteme
eine Datei entsperren (Annahme, der laufende Prozess hat tatsächlich die Sperre erhalten):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Entfernen ist auch nicht atomar, daher zuerst das Umbenennen (was atomar ist) und dann das Entfernen.
Für die Aufrufe von symlink und umbenennen müssen sich beide Dateinamen im selben Dateisystem befinden. Mein Vorschlag: Verwenden Sie nur einfache Dateinamen (keine Pfade) und legen Sie Datei und Sperrung in demselben Verzeichnis ab.
Eine andere Option ist, die noclobber
-Option von Shell zu verwenden, indem Sie set -C
ausführen. Dann schlägt >
fehl, wenn die Datei bereits existiert.
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 verursacht den Aufruf der Shell:
open(pathname, O_CREAT|O_EXCL)
welche die Datei atomar erstellt oder fehlschlägt, wenn die Datei bereits existiert.
Laut einem Kommentar zu BashFAQ 045 kann dies in ksh88
fehlschlagen, 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
Interessanterweise fügt pdksh
das O_TRUNC
-Flag hinzu, ist aber offensichtlich überflüssig:
Sie erstellen entweder eine leere Datei oder tun nichts.
Wie Sie rm
ausführen, hängt davon ab, wie unreine Exits behandelt werden sollen.
Löschen bei sauberem Beenden
Neue Läufe schlagen fehl, bis das Problem behoben wurde, durch das der unreine Exit behoben wurde, 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
Bei Shell-Skripts tendiere ich dazu, die Variable mkdir
über flock
zu gehen, da die Sperren dadurch portabler werden.
Die Verwendung von set -e
reicht in keinem Fall aus. Das Skript wird nur dann beendet, wenn ein Befehl fehlschlägt. Ihre Schlösser bleiben immer noch zurück.
Für eine ordnungsgemäße Bereinigung der Sperre sollten Sie Ihre Traps auf einen solchen Pseudo-Code (angehoben, vereinfacht und nicht getestet, jedoch aus aktiv verwendeten Skripts) setzen:
#=======================================================================
# 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 Traps erzeugen einen Exit, sodass die Funktion __sig_exit
immer ausgeführt wird (mit Ausnahme von SIGKILL), wodurch Ihre Sperren aufgeräumt werden.
Hinweis: Meine Exit-Werte sind keine niedrigen Werte. Warum? Verschiedene Stapelverarbeitungssysteme geben die Erwartungen 0 bis 31 an oder haben Erwartungen. Wenn ich sie auf etwas anderes stelle, kann ich meine Skripte und Stapelströme entsprechend auf den vorherigen Stapeljob oder das Skript reagieren lassen.
Sie können GNU Parallel
dafür verwenden, da er als Mutex fungiert, wenn er als sem
aufgerufen wird. 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, dass das Skript ohne Skript ausgeführt wird, 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 geben sollten (mit --id
), andernfalls wird standardmäßig das steuernde Terminal verwendet.
GNU Parallel
ist auf den meisten Linux/OSX/Unix-Plattformen eine sehr einfache Installation - es handelt sich lediglich um ein Perl-Skript.
Ja wirklich schnell und ja wirklich schmutzig? Dieser Einzeiler oben in Ihrem Skript funktioniert:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Stellen Sie natürlich sicher, dass Ihr Skriptname eindeutig ist. :)
Dieses Beispiel wird in der Mannherde erklärt, aber es bedarf einiger Verbesserungen, da wir Fehler und Beendigungscodes 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, Prozesse auflisten, die ich in der Vergangenheit verwendet habe. Diese Methode ist jedoch komplizierter. Sie sollten Prozesse nach ps auflisten, nach ihrem Namen filtern, zusätzlichen Filter grep -v grep zum Entfernen von Parasiten verwenden und schließlich mit grep -c zählen. und mit Nummer vergleichen. Es ist kompliziert und unsicher
Eine Sperrdatei an einem bekannten Speicherort erstellen und beim Start des Skripts prüfen, ob es existiert? 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.
Hier ist ein Ansatz, bei dem die atomare Verzeichnissperre mit einer Überprüfung auf veraltete Sperre über PID kombiniert wird und ein Neustart durchgeführt wird, wenn nicht mehr aktuell. Dies hängt auch nicht von irgendwelchen Bashmis ab.
#!/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" "[email protected]"
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
Wenn die Einschränkungen von Flock, die bereits an anderer Stelle in diesem Thread beschrieben wurden, für Sie kein Problem sind, 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
Wenn ich einen Debian-Rechner anvisiere, finde ich das lockfile-progs
-Paket eine gute Lösung. procmail
wird auch mit einem lockfile
-Tool geliefert. Manchmal bleibe ich jedoch bei keinem von diesen.
Hier ist meine Lösung, die mkdir
für Atomität und eine PID-Datei zum Erkennen veralteter Sperren verwendet. Dieser Code wird derzeit in einem Cygwin-Setup produziert und funktioniert gut.
Um es zu verwenden, rufen Sie einfach exclusive_lock_require
auf, wenn Sie exklusiven Zugriff auf etwas benötigen. Mit einem optionalen Sperrnamenparameter können Sie Sperren für verschiedene Skripts freigeben. Es gibt auch zwei Funktionen der unteren 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 "[email protected]"
then
exit 1
fi
}
Ich wollte auf Lockfiles, Lockdirs, spezielle Sperrprogramme und sogar auf pidof
verzichten, da es nicht in allen Linux-Installationen vorhanden ist. Wollte auch möglichst einfachen Code (oder zumindest so wenige Zeilen wie möglich) haben. Einfachste if
-Anweisung in einer Zeile:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
Einige Unixe habenlockfile
, was dem bereits erwähntenflock
sehr ähnlich ist.
Aus der Manpage:
lockfile kann verwendet werden, um eine .__ zu erstellen. oder mehr Semaphor-Dateien. Wenn gesperrt - Datei kann nicht alle angegebenen .__ erstellen. Dateien (in der angegebenen Reihenfolge), es Wartet auf die Ruhezeit (Standardeinstellung 8) Sekunden und wiederholt die letzte Datei, die nicht gelungen Sie können das .__ angeben. Anzahl der Wiederholungen bis Fehler wird zurückgegeben. Wenn die Zahl der Wiederholungsversuche ist -1 (Standardeinstellung, d. h., -r-1). Die Sperrdatei wird für immer wiederholt.
Die vorhandenen Antworten beziehen sich entweder auf das 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 nicht ordnungsgemäß unter NFS.
In meinen frühen Tagen der Systemadministration und der Systementwicklung wurde mir gesagt, dass eine sichere und relativ tragbare Methode zum Erstellen einer Sperrdatei darin bestand, eine temporäre Datei zu erstellen, die mkemp(3)
oder mkemp(1)
verwendet, dann identifizierbare Informationen in die temporäre Datei (dh PID) schreiben Verknüpfen Sie die temporäre Datei hart mit der Sperrdatei. Wenn der Link erfolgreich war, haben Sie die Sperre erfolgreich erhalten.
Bei der Verwendung von Sperren in Shell-Skripts platziere ich normalerweise eine obtain_lock()
-Funktion in einem gemeinsam genutzten Profil und füge sie dann aus den Skripts hinzu. Unten ist ein Beispiel meiner 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 die Verwendung der Sperrfunktion:
#!/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, an jedem Exit-Punkt in Ihrem Skript clean_up
aufzurufen.
Ich habe das oben in Linux- und FreeBSD-Umgebungen verwendet.
PID und Lockfiles sind definitiv die zuverlässigsten. Wenn Sie versuchen, das Programm auszuführen, kann es nach der Sperrdatei suchen, welche und falls vorhanden, kann mit ps
feststellen, ob der Prozess noch läuft. Wenn dies nicht der Fall ist, kann das Skript gestartet werden, wobei die PID in der Sperrdatei zu ihrer eigenen aktualisiert wird.
Obwohl die Antwort von bmdhacks fast gut ist, besteht eine geringe Chance, dass das zweite Skript ausgeführt wird, nachdem die Sperrdatei zuerst geprüft wurde und bevor sie geschrieben wurde. Beide schreiben also die Sperrdatei und laufen beide. So stellen Sie sicher, dass es funktioniert:
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 Option noclobber
stellt sicher, dass der Umleitungsbefehl fehlschlägt, wenn die Datei bereits vorhanden ist. Der Redirect-Befehl ist also eigentlich atomar - Sie schreiben und prüfen die Datei mit einem einzigen Befehl. Sie müssen die Sperrdatei nicht am Ende der Datei entfernen - sie wird von der Falle entfernt. Ich hoffe, das hilft Leuten, die es später lesen werden.
P.S. Ich habe nicht gesehen, dass Mikel die Frage bereits richtig beantwortet hat, obwohl er den Trap-Befehl nicht enthielt, um die Wahrscheinlichkeit zu verringern, dass die Sperrdatei nach dem Anhalten des Skripts mit Strg-C übrig bleibt. Das ist also die komplette Lösung
Ich verwende einen einfachen Ansatz, der veraltete Sperrdateien behandelt.
Beachten Sie, dass einige der oben genannten Lösungen, die die PID speichern, die Tatsache ignorieren, dass die PID herumlaufen kann. Daher reicht es nicht aus, nur zu prüfen, ob ein gültiger Prozess mit der gespeicherten PID vorhanden ist, insbesondere für lang laufende Skripts.
Ich verwende noclobber, um sicherzustellen, dass nur ein Skript die Sperrdatei gleichzeitig öffnen und schreiben kann. Außerdem speichere ich genug Informationen, um einen Prozess in der Sperrdatei eindeutig zu identifizieren. Ich definiere die Datenmenge, um einen Prozess eindeutig als pid, ppid, lstart zu identifizieren.
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 einen unaussprechlichen Tod gestorben ist und eine veraltete Sperrdatei hinterlassen hat. Das neue Skript übernimmt dann den Besitz der Sperrdatei und alles ist wieder gut auf der Welt.
Sollte mit mehreren Shells auf mehreren Plattformen arbeiten. 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
Ein Beispiel mit Flock (1), aber ohne Subshell. flock () ed-Datei/tmp/foo wird nie entfernt, aber das spielt keine Rolle, da es flock () und un-flock () ed erhält.
#!/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
Fügen Sie einfach [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "[email protected]" || :
am Anfang Ihres Skripts ein. Es handelt sich hierbei um einen Boilerplate-Code von Man Flock. Um zu verstehen, wie es funktioniert, habe ich ein Skript geschrieben und es gleichzeitig von zwei Konsolen ausgeführt:
#!/bin/bash
if [ "${FLOCKER}" != "$0" ]; then
echo "FLOCKER=$FLOCKER \$0=$0 ($$)"
exec env FLOCKER="$0" flock -en "$0" "$0" "[email protected]" || :
else
echo "FLOCKER equals \$0 = $FLOCKER ($$)"
fi
sleep 10
echo "Process $$ finished"
Ich habe nicht ganz verstanden, wie es funktioniert, aber es scheint, als würde es sich selbst als Lockfile verwenden. FLOCKER
setzt auf "$0"
, um nur einen vernünftigen Wert festzulegen. || :
nichts zu tun, wenn etwas schief gegangen ist.
Es scheint auf Debian 7 nicht zu funktionieren, aber es scheint wieder mit dem experimentellen Paket util-linux 2.25 zu funktionieren. Es schreibt "Flock: ... Textdatei beschäftigt". Es kann durch Deaktivieren der Schreibberechtigung für Ihr Skript überschrieben werden.
Die Verwendung der Sperre des Prozesses ist viel stärker und kümmert sich auch um die unvorhersehbaren Exits. Die Lock_Datei bleibt geöffnet, solange der Prozess ausgeführt wird. Es wird (von 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 finde, dass die Lösung von bmdhack am praktischsten ist, zumindest für meinen Anwendungsfall. Die Verwendung von Flock und Sperrdatei setzt voraus, dass die Sperrdatei mit rm entfernt wird, wenn das Skript beendet wird. Dies kann nicht immer garantiert werden (z. B. kill -9).
Ich möchte an der Lösung von bmdhack eine Kleinigkeit ändern: Es ist wichtig, die Sperrdatei zu entfernen, ohne dass dies für das sichere Funktionieren dieses Semaphors unnötig 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, der Singleton-Seite einfach Folgendes hinzuzufü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, ein Rassenrisiko darstellen, da der Sperrtest und die Set-Operationen keine einzige atomare Aktion sind. Die von lhunath vorgeschlagene Lösung zur Verwendung von mkdir weist jedoch den Fehler auf, dass ein abgebrochenes Skript das Verzeichnis zurücklassen kann, sodass andere Instanzen nicht ausgeführt werden können.
Das Dienstprogramm semaphoric verwendet flock
(wie oben diskutiert, z. B. von presto8), um einen Zählsemaphor zu implementieren. Es ermöglicht eine beliebige Anzahl gleichzeitiger Prozesse, die Sie wünschen. Wir verwenden es, um die Parallelität verschiedener Warteschlangenarbeitsprozesse zu begrenzen.
Es ist wie sem aber viel leichter. (Vollständige Offenlegung: Ich habe es geschrieben, nachdem ich herausfand, dass das Sem für unsere Bedürfnisse viel zu schwer war und es kein einfaches Zähl-Semaphor-Dienstprogramm zur Verfügung stand.)
Bereits millionenfach beantwortet, aber auf andere Weise, ohne externe Abhängigkeiten zu benötigen:
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, wird beim Starten des Skripts geprüft, ob ein Prozess mit der neuesten PID ausgeführt wird.
Dies habe ich nirgendwo erwähnt, es wird gelesen verwendet. Ich weiß nicht genau, ob Lesen tatsächlich atomar ist, aber es hat mir bisher gut gedient ... Es ist saftig, weil es nur bash builtins ist, dies ist ein in Bearbeitung befindliches Verfahren Bei der Implementierung starten Sie den Schließfach-Koprozess und verwenden seine E/A-Funktionen zum Verwalten von Sperren. Das Gleiche können Sie auch interprozessieren, indem Sie einfach die Ziel-E/A von den Schließfachdateideskriptoren in einen Dateisystemdateideskriptor (exec 3<>/file && exec 4</file
) austauschen.
## gives locks
locker() {
locked=false
while read l; do
case "$l" in
lock)
if $locked; then
echo false
else
locked=true
echo true
fi
;;
unlock)
if $locked; then
locked=false
echo true
else
echo false
fi
;;
*)
echo false
;;
esac
done
}
## locks
lock() {
local response
echo lock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
## unlocks
unlock() {
local response
echo unlock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
Der Weg der Herde ist der Weg zu gehen. Denken Sie darüber nach, 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, das Skript selbst in Scharen zu nehmen. Aber natürlich können Sie mit Volldampf vor Erlaubnisproblemen geraten.
warum benutzen wir nicht so etwas
pgrep -f $cmd || $cmd
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
Schauen Sie sich FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/ an: Sie können Befehle und/oder Skripts mit abstrakten Ressourcen synchronisieren, die keine Sperrdateien in einem Dateisystem benötigen. Sie können Befehle, die auf verschiedenen Systemen ausgeführt werden, ohne einen NAS (Network Attached Storage) wie einen NFS-Server (Network File System) synchronisieren.
Im einfachsten Anwendungsfall kann das Serialisieren von "command1" und "command2" genauso einfach sein wie das Ausführen von:
flom -- command1
und
flom -- command2
aus zwei verschiedenen Shell-Skripten.
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
exit 1
fi
Diese einzeilige Antwort stammt von jemandem, der mit Ask Ubuntu Q & A in Verbindung steht []:
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "[email protected]" || :
# This is useful boilerplate code for Shell scripts. Put it at the top of
# the Shell script you want to lock and it'll automatically lock itself on
# the first run. If the env var $FLOCKER is not set to the Shell script
# that is being run, then execute flock and grab an exclusive non-blocking
# lock (using the script itself as the lock file) before re-execing itself
# with the right arguments. It also sets the FLOCKER env var to the right
# value so it doesn't run again.
Ich habe eine einfache Lösung, die auf dem Dateinamen basiert
#!/bin/bash
MY_FILENAME=`basename "$BASH_SOURCE"`
MY_PROCESS_COUNT=$(ps a -o pid,cmd | grep $MY_FILENAME | grep -v grep | grep -v $$ | wc -
l)
if [ $MY_PROCESS_COUNT -ne 0 ]; then
echo found another process
exit 0
if
# Follows the code to get the job done.
Hier ist ein eleganter, ausfallsicherer, schneller & schmutzig Methode, die oben angegebenen Antworten kombiniert.
sh_lock_functions.sh
#!/bin/bash
function sh_lock_init {
sh_lock_scriptName=$(basename $0)
sh_lock_dir="/tmp/${sh_lock_scriptName}.lock" #lock directory
sh_lock_file="${sh_lock_dir}/lockPid.txt" #lock file
}
function sh_acquire_lock {
if mkdir $sh_lock_dir 2>/dev/null; then #check for lock
echo "$sh_lock_scriptName lock acquired successfully.">&2
touch $sh_lock_file
echo $$ > $sh_lock_file # set current pid in lockFile
return 0
else
touch $sh_lock_file
read sh_lock_lastPID < $sh_lock_file
if [ ! -z "$sh_lock_lastPID" -a -d /proc/$sh_lock_lastPID ]; then # if lastPID is not null and a process with that pid exists
echo "$sh_lock_scriptName is already running.">&2
return 1
else
echo "$sh_lock_scriptName stopped during execution, reacquiring lock.">&2
echo $$ > $sh_lock_file # set current pid in lockFile
return 2
fi
fi
return 0
}
function sh_check_lock {
[[ ! -f $sh_lock_file ]] && echo "$sh_lock_scriptName lock file removed.">&2 && return 1
read sh_lock_lastPID < $sh_lock_file
[[ $sh_lock_lastPID -ne $$ ]] && echo "$sh_lock_scriptName lock file pid has changed.">&2 && return 2
echo "$sh_lock_scriptName lock still in place.">&2
return 0
}
function sh_remove_lock {
rm -r $sh_lock_dir
}
sh_lock_usage_example.sh
#!/bin/bash
. /path/to/sh_lock_functions.sh # load sh lock functions
sh_lock_init || exit $?
sh_acquire_lock
lockStatus=$?
[[ $lockStatus -eq 1 ]] && exit $lockStatus
[[ $lockStatus -eq 2 ]] && echo "lock is set, do some resume from crash procedures";
#monitoring example
cnt=0
while sh_check_lock # loop while lock is in place
do
echo "$sh_scriptName running (pid $$)"
sleep 1
let cnt++
[[ $cnt -gt 5 ]] && break
done
#remove lock when process finished
sh_remove_lock || exit $?
exit 0
Sie können auch Folgendes verwenden: https://github.com/sayanarijit/pidlock
Sudo pip install -U pidlock
pidlock -n sleepy_script -c 'sleep 10'
Später zur Party, mit der Idee von @Majal, ist dies mein Skript, um nur eine Instanz von emacsclient GUI zu starten. Damit kann ich die Tastenkombination zum Öffnen oder Zurückspringen zum selben Emacsclient einstellen. Ich habe ein anderes Skript, um emacsclient in Terminals aufzurufen, wenn ich es brauche. Die Verwendung von emacsclient dient hier nur dazu, ein funktionierendes Beispiel zu zeigen, man kann etwas anderes wählen. Dieser Ansatz ist schnell und gut genug für meine kleinen Skripte. Sag mir wo es schmutzig ist :)
#!/bin/bash
# if [ $(pgrep -c $(basename $0)) -lt 2 ]; then # this works but requires script name to be unique
if [ $(pidof -x "$0"|wc -w ) -lt 3 ]; then
echo -e "Starting $(basename $0)"
emacsclient --alternate-editor="" -c "[email protected]"
else
echo -e "$0 is running already"
fi