wake-up-neo.net

Schnelle und schmutzige Möglichkeit, sicherzustellen, dass jeweils nur eine Instanz eines Shell-Skripts ausgeführt wird

Was ist eine schnelle und schmutzige Methode, um sicherzustellen, dass zu einem bestimmten Zeitpunkt nur eine Instanz eines Shell-Skripts ausgeführt wird?

155
raldi

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).

100
bmdhacks

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.

190
Alex B

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) .

147
lhunath

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.

40
Cowan

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.

27
Gunstick

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:

  • symlink
  • umbenennen

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.

22
Stefan Tramm

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
20
Mikel

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.

16
Mark Stinson

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.

14
Mark Setchell

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. :)

13
Majal

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

5
Znik

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.

5
Rob

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
5
bk138

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 
4
presto8

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
}
4
Jason Weathered

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
3
linux_newbie

Einige Unixe habenlockfile, was dem bereits erwähntenflocksehr ä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.

3
dmckee

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.

2
David M. Syzdek

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.

2
Drew Stephens

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

2
NickSoft

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
2
rouble

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
1
sivann

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.

1
user3132194

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 
1
Sudhir Kumar

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.

1
thecowster

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.)

1
Tim Bunce

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.

1
Filidor Wiese

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
}
0
untore

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.

0

warum benutzen wir nicht so etwas 

pgrep -f $cmd || $cmd
0
Jabir Ahmed

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
0
Aupajo

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.

0
tiian
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
    exit 1
fi
0
Rudolf Lörcks

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.
0

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.
0
Gianluca Casati

Hier ist ein eleganter, ausfallsicherer, schneller & schmutzig Methode, die oben angegebenen Antworten kombiniert.

Verwendungszweck

  1. include sh_lock_functions.sh
  2. init mit sh_lock_init
  3. sperren mit sh_acquire_lock
  4. Überprüfen Sie die Sperre mit sh_check_lock
  5. entsperren mit sh_remove_lock

Skriptdatei

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
}

Verwendungsbeispiel

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

Eigenschaften

  • Verwendet eine Kombination aus Datei, Verzeichnis und Prozess-ID zum Sperren, um sicherzustellen, dass der Prozess noch nicht ausgeführt wird
  • Sie können feststellen, ob das Skript vor dem Entfernen der Sperre angehalten wurde (z. B. Prozessabbruch, Herunterfahren, Fehler usw.).
  • Sie können die Sperrdatei überprüfen und verwenden, um das Herunterfahren eines Prozesses auszulösen, wenn die Sperre fehlt
  • Ausführlich, gibt Fehlermeldungen aus, um das Debuggen zu erleichtern
0
Stefan Rogin

Sie können auch Folgendes verwenden: https://github.com/sayanarijit/pidlock

Sudo pip install -U pidlock

pidlock -n sleepy_script -c 'sleep 10'
0
Arijit Basu

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
0
biocyberman