71 votes

Comment empêcher l'exécution simultanée d'un script ?

Je veux empêcher mon script de s'exécuter plus d'une fois à la fois.

Mon approche actuelle est la suivante

  • créer un fichier sémaphore contenant le pid du processus en cours d'exécution
  • lire le fichier, si mon process-id n'y est pas exit (on ne sait jamais...)
  • à la fin du traitement, supprimer le fichier

Afin d'éviter que le processus ne s'arrête, j'ai mis en place une tâche cron qui vérifie périodiquement le fichier s'il est plus ancien que la durée d'exécution maximale autorisée et tue le processus s'il est toujours en cours.

Y a-t-il un risque que je tue un mauvais processus ?

Y a-t-il une meilleure façon d'effectuer cette opération dans son ensemble ?

-Merci !

228voto

Alex B Points 34304

Utilisez flock(1) pour créer un verrou exclusif sur le descripteur de fichier. De cette façon, vous pouvez même synchroniser différentes parties du script.

#!/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

Cela garantit que le code entre ( y ) ne soit exécuté que par un seul processus à la fois et que le processus n'attende pas trop longtemps pour obtenir un verrou.

Avertissement : cette commande particulière fait partie de util-linux . Si vous utilisez un système d'exploitation autre que Linux, il peut ou non être disponible.

27voto

Gunstick Points 231

Vous avez besoin d'une opération atomique, comme flock, sinon cela finira par échouer.

Mais que faire si le troupeau n'est pas disponible. Eh bien, il y a mkdir. C'est aussi une opération atomique. Un seul processus aboutira à un mkdir réussi, tous les autres échoueront.

Donc le code est :

if mkdir /var/lock/.myscript.exclusivelock
then
  # do stuff
  :
  rmdir /var/lock/.myscript.exclusivelock
fi

Vous devez prendre soin des verrous périmés sinon, après un crash, votre script ne sera plus jamais exécuté.

16voto

Mark Stinson Points 127

Pour les scripts shell, j'ai tendance à utiliser l'option mkdir sur flock car cela rend les serrures plus portables.

De toute façon, en utilisant set -e n'est pas suffisant. Cela ne quitte le script que si une commande échoue. Vos verrous seront toujours laissés derrière.

Pour un nettoyage correct des verrous, vous devriez vraiment configurer vos pièges comme ce psuedo code (repris, simplifié et non testé mais provenant de scripts utilisés activement) :

#=======================================================================
# 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

Voici ce qui va se passer. Tous les pièges produiront une sortie, donc la fonction __sig_exit se produira toujours (sauf en cas de SIGKILL), ce qui nettoie vos serrures.

Remarque : mes valeurs de sortie ne sont pas des valeurs basses. Pourquoi ? Divers systèmes de traitement par lots font ou font attendre les chiffres de 0 à 31. En les réglant sur autre chose, je peux faire en sorte que mes scripts et mes flux de lots réagissent en conséquence au travail de lot ou script précédent.

5voto

Znik Points 53

Cet exemple est expliqué dans le manuel, mais il a besoin de quelques améliorations, parce que nous devons devons gérer les bugs et les codes de sortie :

   #!/bin/bash
   #set -e this is usefull 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" ]; 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

Vous pouvez utiliser une autre méthode, énumérer les procédés que j'ai utilisés dans le passé. Mais c'est plus compliqué que la méthode ci-dessus. Vous devez lister les processus par ps, filtrer par son nom, filtre supplémentaire grep -v grep for remove parasite et finalement les compter par grep -c . et comparer avec le nombre. C'est compliqué et incertain.

1voto

Milan Babuškov Points 20423

Je changerais l'étape 2 par la suivante :

  • lire le fichier, si mon identifiant de processus ne s'y trouve pas vérifier si le processus avec le PID du fichier est en cours d'exécution. Si oui, quittez, si non, écrasez simplement le fichier de verrouillage (sémaphore).

Cela vous permettra d'éviter les problèmes de fichiers de verrouillage périmés.

En ce qui concerne votre autre question, il y a toujours une chance de tuer le mauvais processus, donc assurez-vous de vérifier toutes les informations sur le script en cours d'exécution comme le nom du fichier script. Et, bien sûr, n'oubliez pas de supprimer le fichier de verrouillage par la suite.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X