ubuntuusers.de

script soll prüfen ob es selber schon läuft

Status: Ungelöst | Ubuntu-Version: Ubuntu GNOME 14.04 (Trusty Tahr)
Antworten |

compi

Anmeldungsdatum:
8. September 2009

Beiträge: 49

ich benötige eine Funktion die abprüft ob ein script, oder besser, ob das Script selber schon läuft.

könnt ihr mir da helfen?

Gruß compi

unbuntuS12

Anmeldungsdatum:
2. Juni 2010

Beiträge: 1816

Hallo,

eine Möglichkeit ist hier beschrieben. Der dritte Bullet-Point der bestbewerteten Antwort drückt genau deinen Anwendungsfall aus.

Benno-007

Anmeldungsdatum:
28. August 2007

Beiträge: 29240

Wohnort: Germany

Du kannst das Script auch so starten lassen (oder darin so aufbereiten):

pidof script || script

Dann startet das Script nur, wenn es noch nicht läuft, eindeutiger Name vorausgesetzt.

Alternativ kannst du auch eine Variable oder Datei anlegen lassen

touch /tmp/script123-is-running

, deren Existenz du überprüfst:

if [ -f /tmp/script123-is-running ]; then
  scriptname
  oder_scriptinhalt-im-script-hier-als-block
fi

Am Ende des Scriptes natürlich wieder löschen lassen - spätestens beim Neustart wird /tmp sowieso automatisch geleert.

horstpenner

Anmeldungsdatum:
7. Februar 2016

Beiträge: 364

compi schrieb:

ich benötige eine Funktion die abprüft ob ein script, oder besser, ob das Script selber schon läuft.

Du möchtest also eine bool'sche Funktion schreiben, die prüft, ob sie selbst ausgeführt wird. Das ist doch sinnlos, weil sie nur true zurückliefern kann - sonst wäre sie ja gar nicht aufgerufen worden.

Benno-007

Anmeldungsdatum:
28. August 2007

Beiträge: 29240

Wohnort: Germany

Es geht wohl darum, Mehrfachstarts zu vermeiden, um mehrere Instanzen desselben Programms auf einmal zu vermeiden.

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13176

horstpenner schrieb:

compi schrieb:

ich benötige eine Funktion die abprüft ob ein script, oder besser, ob das Script selber schon läuft.

Du möchtest also eine bool'sche Funktion schreiben, die prüft, ob sie selbst ausgeführt wird. Das ist doch sinnlos, weil sie nur true zurückliefern kann - sonst wäre sie ja gar nicht aufgerufen worden.

Es geht darum zu erkennen, ob es bereits eine andere Instanz des Skriptes gibt. Das ist überhaupt nicht sinnlos.

Ein übliches Verfahren, das bereits genannt wurde, ist eine Pid-Datei. Man kann auch sehr schön flock nutzen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/bin/sh

cmd="${0##*/}"
file="/tmp/$cmd"

touch "$file"

(
  flock -n 9 || exit
  echo lauf $$
  sleep 5 # main code
) 9<"$file"

horstpenner

Anmeldungsdatum:
7. Februar 2016

Beiträge: 364

rklm schrieb:

Es geht darum zu erkennen, ob es bereits eine andere Instanz des Skriptes gibt. Das ist überhaupt nicht sinnlos.

Okay, ich dachte, es ginge darum, zu prüfen ob die aktuelle Instanz läuft...

coram

Anmeldungsdatum:
17. Januar 2015

Beiträge: 645

Wohnort: Freiburg

Mein Skript selbsttest.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

if [ "$(pidof -x selbsttest.sh)" != "$$" ]; then
    echo "Eine andere Instanz läuft bereits!"
    exit 1
else
    echo "Es läuft noch keine andere Instanz."
    while [ 1 ]; do
      sleep 1
    done &
fi

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13176

coram schrieb:

Mein Skript selbsttest.sh:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/bash

if [ "$(pidof -x selbsttest.sh)" != "$$" ]; then
    echo "Eine andere Instanz läuft bereits!"
    exit 1
else
    echo "Es läuft noch keine andere Instanz."
    while [ 1 ]; do
      sleep 1
    done &
fi

Guter Ansatz! Aber wozu die Schleife im Hintergrund, die nichts tut und nie terminiert? (Du kannst die Schleifenbedingung [ 1 ] übrigens durch : oder meinetwegen auch true ersetzen.

Ich hätte das dann ja eher unabhängig vom Namen gemacht, weil man das sonst immer ändern muss, wenn sich der Skriptname ändert:

1
2
3
4
5
6
7
8
9
#!/bin/sh

if [ "$(pidof -x "$0")" != "$$" ]
  echo 'Eine andere Instanz läuft bereits!'
  exit 1
fi

echo 'Es läuft noch keine andere Instanz.'
echo 'Die Arbeit kann beginnen...'

coram

Anmeldungsdatum:
17. Januar 2015

Beiträge: 645

Wohnort: Freiburg

rklm schrieb:

wozu die Schleife im Hintergrund, die nichts tut und nie terminiert? (Du kannst die Schleifenbedingung [ 1 ] übrigens durch : oder meinetwegen auch true ersetzen.

Die Schleife war nur als Dummy-Main-Code gedacht. Das Skript musste ich ja irgendwie am Laufen halten, um zu testen, ob beim zweiten Aufruf die bereits laufende Instanz erkannt wird.

Ich hätte das dann ja eher unabhängig vom Namen gemacht, weil man das sonst immer ändern muss, wenn sich der Skriptname ändert

Danke für den Hinweis, Du hast recht, so ist das universeller einsetzbar. An Deiner Modifikation erkenne ich natürlich auch, dass es für den "Main Code" keines else-Abschnittes bedarf.

Als Hobby-Coder bin ich ja immer froh, wenn ich dazu lernen kann! Grundsätzlich würde mich deshalb noch interessieren, welchen Vorteil die Lösungen mit PID-Datei oder flock haben – die im Gegensatz zu meinem Skript ja eine zusätzliche Datei erforderlich machen bzw. anlegen?

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13176

coram schrieb:

rklm schrieb:

wozu die Schleife im Hintergrund, die nichts tut und nie terminiert? (Du kannst die Schleifenbedingung [ 1 ] übrigens durch : oder meinetwegen auch true ersetzen.

Die Schleife war nur als Dummy-Main-Code gedacht. Das Skript musste ich ja irgendwie am Laufen halten, um zu testen, ob beim zweiten Aufruf die bereits laufende Instanz erkannt wird.

Ja, aber dafür muss ja der Hauptcode nicht im Hintergrund laufen.

Ich hätte das dann ja eher unabhängig vom Namen gemacht, weil man das sonst immer ändern muss, wenn sich der Skriptname ändert

Danke für den Hinweis, Du hast recht, so ist das universeller einsetzbar. An Deiner Modifikation erkenne ich natürlich auch, dass es für den "Main Code" keines else-Abschnittes bedarf.

😬

Als Hobby-Coder bin ich ja immer froh, wenn ich dazu lernen kann! Grundsätzlich würde mich deshalb noch interessieren, welchen Vorteil die Lösungen mit PID-Datei oder flock haben – die im Gegensatz zu meinem Skript ja eine zusätzliche Datei erforderlich machen bzw. anlegen?

Eine interessante Frage! Da muss ich einen Moment nachdenken.

Die Lösung mit flock kann man auch ohne separate Datei machen, indem man einfach das Skript selbst nimmt. Das ist mir gerade erst eingefallen. ☺ Außerdem würde ich die Subshell weglassen, so dass die Lösung jetzt so aussähe:

1
2
3
4
5
6
7
8
#!/bin/sh

exec 9<"$0"
flock -n 9 || exit

# main code
echo lauf $$
sleep 5 

Jetzt zu den Vorteilen der einzelnen Lösungen:

  • flock

    • Weil das Locken im Dateisystem atomar ist (außer bei Netzwerkdateisystemen wie NFS, aber wir gehen mal von einer lokalen Lock-Datei aus) und das Lock mit dem Schließen des Dateideskriptors (spätestens beim Prozess-Exit) automatisch vom Kernel gelöst wird, ist das Verfahren am robustesten - es gibt keine Race Conditions.

    • Die Variante kann leicht um eine Wartezeit ergänzt werden, so dass das Skript doch noch läuft, falls die anderen Instanz innerhalb von n Sekunden terminiert (Option "-w" von flock).

    • Ich glaube, es hat noch einen klitzekleinen Vorteil gegenüber dem Verfahren mit pidof für den - zugegeben sehr theoretischen Fall - dass das Skript umbenannt wird, während es läuft: in dem Fall greift pidof ins Leere und würde ein zweites Mal mit neuem Namen ausführen, während das Lock bei der Umbenennung (zumindest innerhalb eines Dateisystems) mitwandert.

    • Der einzige Nachteil der Lösung mit flock erscheint mir, dass FD 9 geschlossen wird, falls er bereits aus irgendeinem Grund offen ist. U.U. leidet dann irgendeine Funktionalität, wobei ich das bei einem Skript eher für eine theoretische Gefahr halte.

  • PID-Datei

    • Ein etabliertes Verfahren, das insbesondere von Systemdiensten gerne benutzt wird. Heutzutage (mit Systemd) halte ich es für veraltet.

    • Man kann die PID des laufenden Programms direkt aus der Datei lesen. (Wobei die heutzutage mit pidof oder pgrep genau so leicht zu ermitteln ist.)

    • Je nach Vergabe der Berechtigungen an der PID-Datei können u.U. auch andere Benutzer prüfen, ob das Programm läuft.

    • Funktioniert am besten mit Programmen in anderen Programmiersprachen (also nicht in Skripten), da man dort wenigstens das Öffnen der PID-Datei atomar machen kann, d.h., der Systemaufruf kommt nur erfolgreich zurück, wenn die Datei noch nicht da ist und nicht gerade von einem anderen Prozess angelegt wird.

    • Nachteil: jeder Prozess mit den passenden Berechtigungen kann die Datei löschen.

    • Nachteil: es gibt eine Race Condition, die dazu führt, dass zwei Prozess die PID-Datei schreiben und dann auch parallel laufen.

  • pidof

    • Finde ich recht elegant.

    • Die Race Condition ist vernachlässigbar, da im Zweifel einfach keine Instanz läuft. (Im Gegensatz zur PID-Datei-Verfahren, bei dem mehrere Prozesse laufen können.)

    • Wenn man auf die Ausgabe verzichtet, kann man das recht gut zu einem Einzeiler machen (s.u.).

Ich glaube, ich würde flock nehmen, weil es ein wenig exotisch ist. ☺ Daneben ist es auch sehr robust. pidof ist auch gut, aber von den PID-Dateien würde ich in Skripten eher die Finger lassen. Vielleicht habe ich aber auch noch einen Aspekt übersehen und jemand anderes kommt damit um die Ecke.

1
[ "$(pidof -x "$0")" = $$ ] || exit

coram

Anmeldungsdatum:
17. Januar 2015

Beiträge: 645

Wohnort: Freiburg

Vielen Dank für Deine interessanten und sehr ergiebigen Ausführungen! Spätestens jetzt hat es mich wirklich gejuckt, Deine Lösungen selbst auszuprobieren und mit dem "exotischen" flock ein wenig zu experimentieren.

In zwei Punkten kam ich dabei bzgl. flock doch zu einer vielleicht leicht kritischeren Einschätzung:

1. Folgende Aussage rklm schrieb:

  • Ich glaube, es hat noch einen klitzekleinen Vorteil gegenüber dem Verfahren mit pidof für den - zugegeben sehr theoretischen Fall - dass das Skript umbenannt wird, während es läuft: in dem Fall greift pidof ins Leere und würde ein zweites Mal mit neuem Namen ausführen, während das Lock bei der Umbenennung (zumindest innerhalb eines Dateisystems) mitwandert.

fand ich leider nicht bestätigt. Nach einer Umbenennung wird doch von einer neuen Instanz die alte Lockdatei ignoriert und eine neue erzeugt!

Dies ließe sich umgehen, indem man für die Lockdatei einen festen, also vom Skriptnamen unabhängigen Namen vorgibt.

Deinen Ansatz

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
  #!/bin/sh

cmd="${0##*/}"
file="/tmp/$cmd"

touch "$file"

(
  flock -n 9 || exit
  echo lauf $$
  sleep 5 # main code
) 9<"$file"

habe ich dafür wie folgt variiert:

1
2
3
4
5
6
7
#!/bin/bash

(
  flock -n 9 || exit
  echo lauf $$
  sleep 5 # main code
) 9>/tmp/lockfile

(wobei ich ein ganze begriffsstutzige Weile brauchte, bis ich darauf kam, dass ich Deine Umleitung in der letzten Zeile umkehren konnte/musste ... 😉 ). Bei dieser Variante muss man allerdings aufpassen, dass man nicht versehentlich für verschiedene Skripte den gleichen Lockfilenamen vergibt.

Aber natürlich ist die Umbennung eines laufenden Skripts, wie Du sagst, ein doch ziemlich theoretischer und deshalb wahrscheinlich vernachlässigbarer Fall.

2. Ein etwas schwerwiegenderer Einwand ist vielleicht folgender:

Wird die Ausführung des obigen Skripts (egal ob in Deiner oder meiner Variante) durch einen kill-Befehl oder eine unvorhergesehene Panne beendet, kann es erst wieder gestartet werden, wenn man nicht vergisst, vorher die Lockdatei zu löschen.

Nicht betroffen von diesem Problem ist jedoch Deine, wie ich finde, geniale Lösung, dass das Skript sich selbst "flockt". Deshalb: Mein absoluter Favorit!!! 👍

Nochmals herzlichen Dank für Dein "Privat-Tutorium", von dem aber vermutlich auch der Themenstarter (der sich nicht wieder/ noch nicht gemeldet hat) auf der Suche nach einer praxisgerechten Lösung profitieren kann!

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13176

coram schrieb:

Vielen Dank für Deine interessanten und sehr ergiebigen Ausführungen! Spätestens jetzt hat es mich wirklich gejuckt, Deine Lösungen selbst auszuprobieren und mit dem "exotischen" flock ein wenig zu experimentieren.

In zwei Punkten kam ich dabei bzgl. flock doch zu einer vielleicht leicht kritischeren Einschätzung:

1. Folgende Aussage rklm schrieb:

  • Ich glaube, es hat noch einen klitzekleinen Vorteil gegenüber dem Verfahren mit pidof für den - zugegeben sehr theoretischen Fall - dass das Skript umbenannt wird, während es läuft: in dem Fall greift pidof ins Leere und würde ein zweites Mal mit neuem Namen ausführen, während das Lock bei der Umbenennung (zumindest innerhalb eines Dateisystems) mitwandert.

fand ich leider nicht bestätigt. Nach einer Umbenennung wird doch von einer neuen Instanz die alte Lockdatei ignoriert und eine neue erzeugt!

Ich weiß nicht, was Du genau getestet hast - bei mir funktioniert es. Achso, Du hast wahrscheinlich den Namen der Lockdatei vom Namen des Skriptes abgeleitet. Meine Beschreibung oben bezog sich auf den Fall, dass das Skript selbst zum Locken benutzt wird oder man wenigstens den Namen der Lockdatei nicht ändert.

Dieses steht in x.sh mit passenden Rechten für die Ausführung:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#!/bin/sh

echo start $$
trap 'echo exit $$' 0

exec 9<"$0"
flock -n 9 || exit 1

# main code
echo lauf $$
sleep 5

Auf der Konsole:

1
2
3
4
5
6
7
8
9
$ ./x.sh & sleep 1; mv -v x.sh xx.sh; ./xx.sh; sleep 7
[1] 7964
start 7964
lauf 7964
‘x.sh’ -> ‘xx.sh’
start 7969
exit 7969
exit 7964
[1]+  Done                    ./x.sh

Wie Du siehst, gibt es nur eine Ausgabe "lauf", was dem Hauptprogramm entspricht.

2. Ein etwas schwerwiegenderer Einwand ist vielleicht folgender:

Wird die Ausführung des obigen Skripts (egal ob in Deiner oder meiner Variante) durch einen kill-Befehl oder eine unvorhergesehene Panne beendet, kann es erst wieder gestartet werden, wenn man nicht vergisst, vorher die Lockdatei zu löschen.

Das ist falsch. Zum einen wird das Lock automatisch vom Kernel freigegeben, wenn der Prozess - auf welche Art auch immer - terminiert. Danach kann ein neuer Prozess das Lock wieder ziehen.

Zum zweiten beruht der Ansatz mit flock auf dem Locken eines bestimmten Objektes (also einem Inode) im Dateisystem bzw. durch den Kernel. Das bedingt, dass die Datei, die zum Locken benutzt wird, immer da bleibt und denselben Inode hat. Andernfalls handelt es sich um zwei verschiedene Locks, die sich dementsprechend nicht ausschließen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ ./x.sh & sleep 1; cp -av x.sh xx.sh; rm x.sh; mv -v xx.sh x.sh; ./x.sh; sleep 7
[1] 23777
start 23777
lauf 23777
‘x.sh’ -> ‘xx.sh’
‘xx.sh’ -> ‘x.sh’
start 24706
lauf 24706
exit 23777
exit 24706
[1]+  Done                    ./x.sh

Du siehst zwei Ausgaben "lauf", was bedeutet, dass beide Prozesse auf unterschiedliche Locks gehen.

Nicht betroffen von diesem Problem ist jedoch Deine, wie ich finde, geniale Lösung, dass das Skript sich selbst "flockt". Deshalb: Mein absoluter Favorit!!! 👍

Ja, das scheint wirklich die eleganteste Lösung zu sein.

Nochmals herzlichen Dank für Dein "Privat-Tutorium", von dem aber vermutlich auch der Themenstarter (der sich nicht wieder/ noch nicht gemeldet hat) auf der Suche nach einer praxisgerechten Lösung profitieren kann!

Bitte! Ich fand es auch interessant, da mal ein bisschen tiefer einzusteigen.

Einen schönen Sonntag noch!

robert

rklm Team-Icon

Projektleitung

Anmeldungsdatum:
16. Oktober 2011

Beiträge: 13176

PS: Ich habe gesehen, dass ich dazu ja bereits etwas auf meiner Benutzerseite hatte. ☺ Ich habe jetzt mal den Link zu diesem Thema eingefügt und werde den Rest ggf. auch noch mal ein wenig anpassen.

frostschutz

Avatar von frostschutz

Anmeldungsdatum:
18. November 2010

Beiträge: 7782

flock ist natürlich genial. 👍

Für einfachste Ansprüche mache ich das jedoch mit mkdir...

mkdir /tmp/mein-script-dings || exit

echo Das hier läuft jetzt nur einmal.

rmdir /tmp/mein-script-dings

mkdir schlägt fehl wenn das Verzeichnis schon existiert, und die Existenz eines Verzeichnisses sollte auch mehr oder weniger atomar sein.

Man muss nur drauf achten daß das Script nicht zwischendurch aussteigt, also das Verzeichnis am Ende wieder weg ist. Sonst ist es blockiert bis man manuell eingreift.

Ansonsten lasse ich es auch einfach per screen laufen, dazu habe ich auch ein screen-manager Script in meinem GitHub, das verträgt zur Not auch interaktive Anwendungen die nur einmal laufen sollen.

Antworten |