2009-09-17 7 views
13

Ich habe ein Bash-Skript Ich möchte alle 5 Minuten von Cron laufen ... aber es besteht die Möglichkeit, dass der vorherige Lauf des Skripts noch nicht fertig ist ... in diesem Fall möchte ich, dass der neue Lauf einfach beendet wird. Ich möchte nicht nur auf eine Sperrdatei in/tmp .. Ich möchte sicherstellen, dass der Prozess tatsächlich ausgeführt wird, bevor ich die Sperrdatei (oder was auch immer) honoriere ...Wie stelle ich sicher, dass mein Bash-Skript noch nicht läuft?

Hier ist was ich habe bisher aus dem internet gestohlen ... wie schärfe ich es ein bisschen auf? oder gibt es einen ganz anderen Weg, der besser ist?

if [ -f /tmp/mylockFile ] ; then 
    echo 'Script is still running' 
else 
    echo 1 > /tmp/mylockFile 
    /* Do some stuff */ 
    rm -f /tmp/mylockFile 
fi 
+0

möglich Duplikat of [Schnell und sicher, dass nur eine Instanz eines Shell-Skripts gleichzeitig ausgeführt wird] (http://stackoverflow.com/questions/185451/quick-and-dirtyway-to-ensure-only- one-of-shell-script-is-running-at) – user

Antwort

21
# Use a lockfile containing the pid of the running process 
# If script crashes and leaves lockfile around, it will have a different pid so 
# will not prevent script running again. 
# 
lf=/tmp/pidLockFile 
# create empty lock file if none exists 
cat /dev/null >> $lf 
read lastPID < $lf 
# if lastPID is not null and a process with that pid exists , exit 
[ ! -z "$lastPID" -a -d /proc/$lastPID ] && exit 
echo not running 
# save my pid in the lock file 
echo $$ > $lf 
# sleep just to make testing easier 
sleep 5 

In diesem Skript gibt es mindestens eine Racebedingung. Verwenden Sie es nicht für ein Lebenserhaltungssystem, lol. Aber es sollte für Ihr Beispiel gut funktionieren, weil Ihre Umgebung nicht zwei Skripte gleichzeitig startet. Es gibt viele Möglichkeiten, mehr atomare Sperren zu verwenden, aber sie hängen im Allgemeinen davon ab, ob eine bestimmte Sache optional installiert ist oder anders auf NFS usw. funktioniert.

+1

Warum 'cat/dev/null >> $ lf' statt' touch $ lf'? Warum "schlafen", wenn es keine Schleife gibt? –

+0

'touch (1)' ist auch gut. Der Schlaf dient nur der Demonstration, um mein Skriptfragment selbst testbar zu machen. – DigitalRoss

+1

(Das heißt, die PID wird verschwinden, sobald das Skript beendet ist) – DigitalRoss

4

Wenn Sie den Prozess Existenz überprüfen möchten, schauen Sie am Ausgang des

ps aux | grep your_script_name

Wenn es da ist, ist es nicht tot ist ...

Wie in der darauf hingewiesen, Kommentare und andere Antworten, die Verwendung der PID in der Lockfile gespeichert ist viel sicherer und ist der Standardansatz die meisten Apps nehmen. Ich mache das einfach, weil es praktisch ist und ich die Eckenfälle (z. B. Editieren der Datei, wenn die cron ausgeführt wird) in der Praxis fast nie sehe.

+1

Zusätzlich können Sie die PID des aktuellen BASH-Prozesses in dieser Sperrdatei speichern. BASH stellt eine Variable '$$' (abzüglich der Anführungszeichen) zur Verfügung, die diese Zahl angibt. Siehe hier: http://tldp.org/LDP/abs/html/internalvariables.html für einige dieser Variablen – Buggabill

+0

-1 Was ist, wenn "emacs your_script_name" einer der laufenden Prozesse ist? – mob

+0

Sie haben beide absolut Recht. Die in der Datei gespeicherte PID wäre ein viel besserer Weg. Ich tendiere dazu einfach so, weil ich faul bin, meine Skripts sind nicht missionskritisch und funktionieren normalerweise (ziemlich selten würde ich das Skript bearbeiten, wenn der Cron ausgeführt wird). –

4

Speichern Sie Ihre PID in mylockFile. Wenn Sie überprüfen müssen, suchen Sie nach ps für den Prozess mit der PID, die Sie aus der Datei gelesen haben. Wenn es existiert, wird Ihr Skript ausgeführt.

0

Sie können immer nur:

if ps -e -o cmd | grep scriptname > /dev/null; then 
    exit 
fi 

aber Ich mag die lockfile selbst, so würde ich dies auch ohne die Sperrdatei nicht.

+1

Je nachdem, wie portierbar das sein muss, müssen Sie möglicherweise Ihre ps-Optionen anpassen und Sie können -q zu grep anstelle von/dev/null hinzufügen –

+2

Kein Mitarbeiterbeweis genügt: Jemand könnte ausgeführt werden " less scriptename "und das würde ausreichen, um zu verhindern, dass das Skript läuft – mob

+0

mobrule: Ya, hätte nie daran gedacht, nur weil ich es nie so machen würde ;-) Das Lockfile ist besser. –

9

Vielleicht möchten Sie sich die Manpage für die flock Befehl, wenn Sie Glück haben, es auf Ihre Verteilung zu bekommen.

 
NAME 
     flock - Manage locks from shell scripts 
SYNOPSIS 
     flock [-sxon] [-w timeout] lockfile [-c] command... 
+0

Diese Lösung vermeidet den Wettlauf zwischen der Überprüfung und dem Erhalt der Sperre am saubersten, im Gegensatz zu einigen anderen vorgeschlagenen Lösungen. Wenn du 'flock (1)' hast (das kommt mit util-linux in debian/Ubuntu usw.) benutze es definitiv. Für den OP-Anwendungsfall benötigen Sie eine kurze Zeitüberschreitung (z. B. -w 1). Wenn der Befehl bereits ausgeführt wird, gibt flock nach 1 Sekunde auf und führt den Befehl nicht aus, andernfalls wird er die Sperre erhalten und den Befehl starten. – arielf

+0

Anstatt "-w 1" zu verwenden, um Timeout zu erhalten und nach 1 Sekunde zu beenden, könnte das OP '-n, --nb, --nonblock Fail (mit einem Exit-Code von 1) verwenden, anstatt zu warten, wenn die Sperre nicht sofort ausgeführt werden kann erworben. "Siehe Beispiel in meiner Antwort, die leider hinzugefügt wurde, nachdem dieses Thema aktiv war. – mattst

1

In einigen Fällen möchten Sie in der Lage sein zu unterscheiden, die das Skript ausgeführt wird und einige Parallelität erlauben, aber nicht alle. In diesem Fall können Sie benutzerspezifische, per-tty- oder cron-spezifische Sperren verwenden.

Sie können Umgebungsvariablen wie $ USER oder die Ausgabe eines Programms wie tty verwenden, um den Dateinamen zu erstellen. Für cron können Sie eine Variable in der Datei crontab festlegen und in Ihrem Skript dafür testen.

6

Verwenden Sie niemals eine Sperre Datei verwenden Sie immer eine Sperre Verzeichnis. In Ihrem speziellen Fall ist dies nicht so wichtig, da der Start des Skripts in Intervallen von 5 Minuten geplant ist. Aber wenn Sie diesen Code für einen Webserver cgi-Skript wiederverwenden, sind Sie Toast.

Dies hat ein Problem, wenn Sie eine veraltete Sperre haben, bedeutet die Sperre ist da, aber kein damit verbundener Prozess. Dein Cron wird niemals rennen.

Warum ein Verzeichnis verwenden? Weil mkdir eine atomare Operation ist. Nur jeweils ein Prozess kann ein Verzeichnis erstellen, alle anderen Prozesse erhalten einen Fehler. Dies funktioniert sogar über gemeinsame Dateisysteme und wahrscheinlich sogar zwischen verschiedenen Betriebssystemtypen.

+0

Lock-Dateien funktionieren nicht über NFS - was bei Shared Hosting üblich ist. Wir verwenden häufig Sperrverzeichnisse aus dem gleichen Grund. –

3

Wenn Sie eine Sperrdatei verwenden, sollten Sie sicherstellen, dass die Sperrdatei immer entfernt wird. Sie können dies tun, mit ‚Fall‘:

if (set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then 
    trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT 
    echo "Locking succeeded" >&2 
    rm -f "$lockfile" 
else 
    echo "Lock failed - exit" >&2 
    exit 1 
fi 

Die noclobber Option macht die Erstellung von lockfile atomar, wie ein Verzeichnis.

0

können Sie verwenden diese:

pgrep -f "/bin/\w*sh .*scriptname" | grep -vq $$ && exit 
3

Als One-Liner und wenn Sie nicht wollen, eine Sperrdatei (zB b/c/ein Nur-Lese-Dateisystem, etc.) verwenden

test "$(pidof -x $(basename $0))" != $$ && exit 

Es prüft, ob die vollständige Liste der PID, die den Namen Ihres Skripts tragen, der aktuellen PID entspricht. Das "-x" überprüft auch den Namen von Shell-Skripten.

Bash macht es noch kürzer und schneller:

[[ "$(pidof -x $(basename $0))" != $$ ]] && exit 
1

Ich habe versucht, dieses Problem heute zu lösen und ich kam mit dem unten:

COMMAND_LINE="$0 $*" 
JOBS=$(SUBSHELL_PID=$BASHPID; ps axo pid,command | grep "${COMMAND_LINE}" | grep -v $$ | g rep -v ${SUBSHELL_PID} | grep -v grep) 
if [[ -z "${JOBS}" ]] 
then 
    # not already running 
else 
    # already running 
fi 

Das auf $BASHPID beruht, die die PID enthält Innerhalb einer Subshell ($$ in der Subshell ist die Eltern-PID). Dies hängt jedoch von Bash v4 ab und ich musste dies auf OSX mit Bash v3.2.48 ausführen. Ich kam schließlich mit einer anderen Lösung und es ist sauberer:

JOBS=$(sh -c "ps axo pid,command | grep \"${COMMAND_LINE}\" | grep -v grep | grep -v $$") 
0

Da eine Socket-Lösung wird noch nicht erwähnt worden ist es beachtenswert, dass Steckdosen als wirksamer mutexes verwendet werden. Socket-Erstellung ist eine atomare Operation, wie mkdir ist wie Gunstick hingewiesen, so dass eine Steckdose als Schloss oder Mutex verwendet werden kann.

Tim Kay's Perl-Skript 'Solo' ist ein sehr kleines und effektives Skript, um sicherzustellen, dass nur eine Kopie eines Skripts gleichzeitig ausgeführt werden kann. Es wurde speziell für die Verwendung mit Cron-Jobs entwickelt, obwohl es auch für andere Aufgaben perfekt funktioniert, und ich habe es sehr effektiv für Non-Crob-Jobs verwendet.

Solo hat einen Vorteil gegenüber den anderen bisher genannten Techniken, da die Überprüfung außerhalb des Skripts erfolgt, von dem Sie nur eine Kopie ausführen möchten. Wenn das Skript bereits ausgeführt wird, wird nie eine zweite Instanz dieses Skripts gestartet. Dies ist im Gegensatz zum Isolieren eines Codeblocks innerhalb des Skripts, der durch eine Sperre geschützt ist. BEARBEITEN: Wenn flock in einem Cron-Job und nicht innerhalb eines Skripts verwendet wird, können Sie damit auch verhindern, dass eine zweite Instanz des Skripts gestartet wird - siehe Beispiel unten.

Hier ist ein Beispiel dafür, wie Sie es mit cron verwenden könnte:

*/5 * * * * solo -port=3801 /path/to/script.sh args args args 

# "/path/to/script.sh args args args" is only called if no other instance of 
# "/path/to/script.sh" is running, or more accurately if the socket on port 3801 
# is not open. Distinct port numbers can be used for different programs so that 
# if script_1.sh is running it does not prevent script_2.sh from starting, I've 
# used the port range 3801 to 3810 without conflicts. For Linux non-root users 
# the valid port range is 1024 to 65535 (0 to 1023 are reserved for root). 

* * * * * solo -port=3802 /path/to/script_1.sh 
* * * * * solo -port=3803 /path/to/script_2.sh 

# Flock can also be used in cron jobs with a distinct lock path for different 
# programs, in the example below script_3.sh will only be started if the one 
# started a minute earlier has already finished. 

* * * * * flock -n /tmp/path.to.lock -c /path/to/script_3.sh 

Links:

Hoffnung das hilft.

0

Sie können this verwenden.

Ich werde die Lösung hier einfach schamlos kopieren und einfügen, da es eine Antwort auf beide Fragen ist (ich würde behaupten, dass es für diese Frage eigentlich besser passt).

Nutzungs

  1. umfassen sh_lock_functions.sh
  2. init sh_lock_init
  3. Schloss sh_acquire_lock
  4. Kontrollschloss sh_check_lock
  5. un verriegeln mit sh_remove_lock

Script

Datei

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 
} 

Anwendungsbeispiel

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

  • wird eine Kombination aus Datei, ein Verzeichnis und Prozess-ID zu sperren, um sicherzustellen, dass der Prozess nicht bereits läuft
  • Sie erkennen können, ob das Skript vor Schloss Entfernung gestoppt (zB. Prozess beenden, Herunterfahren, Fehler etc.)
  • Sie die Sperrdatei überprüfen können, und es verwenden, einen Prozess Shutdown auslösen, wenn das Schloss
  • Ausführlich fehlt, gibt Fehlermeldungen einfacher debuggen
Verwandte Themen