138 votes

Quel est le meilleur moyen de s'assurer qu'une seule instance d'un script Bash est en cours d'exécution ?

Quel est le moyen le plus simple/le plus efficace de s'assurer qu'une seule instance d'un script donné est en cours d'exécution - en supposant qu'il s'agisse de Bash sur Linux ?

En ce moment, je le fais :

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh

mais il présente plusieurs problèmes :

  1. il met la vérification en dehors de script
  2. il ne me laisse pas exécuter le même script à partir de comptes séparés - ce que j'aimerais parfois.
  3. -C vérifie uniquement les 14 premiers caractères du nom du processus

Bien sûr, je peux écrire ma propre gestion du fichier pidfile, mais je sens qu'il devrait y avoir un moyen simple de le faire.

178voto

przemoc Points 1317

Le verrouillage consultatif est utilisé depuis des lustres et il peut être utilisé dans les scripts de bash. Je préfère le simple flock (de util-linux[-ng] ) sur lockfile (de procmail ). Et n'oubliez jamais le piège à la sortie (sigspec ==). EXIT o 0 le piégeage de signaux spécifiques est superflu) dans ces scripts.

En 2009, j'ai publié mon boilerplate script verrouillable (initialement disponible sur ma page wiki, aujourd'hui disponible sous le nom de Gist ). Transformer cela en une-instance-par-utilisateur est trivial. En l'utilisant, vous pouvez aussi facilement écrire des scripts pour d'autres scénarios nécessitant un certain verrouillage ou une certaine synchronisation.

Voici le modèle mentionné pour votre commodité.

#!/bin/bash
# SPDX-License-Identifier: MIT

## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate

### HEADER ###

LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99

# PRIVATE
_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

# ON START
_prepare_locking

# PUBLIC
exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock

### BEGIN OF SCRIPT ###

# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1

# Remember! Lock file is removed when one of the scripts exits and it is
#           the only script holding the lock or lock is not acquired at all.

54voto

Jake Biesinger Points 717

Je pense flock est probablement la variante la plus facile (et la plus mémorable). Je l'utilise dans une tâche cron pour encoder automatiquement dvds y cds

# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command

Utilisez -w pour les délais d'attente ou ne pas tenir compte des options pour attendre que le verrou soit libéré. Enfin, la page de manuel montre un bel exemple pour les commandes multiples :

   (
     flock -n 9 || exit 1
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile

15voto

Utiliser bash set -o noclobber et tenter d'écraser un fichier commun.

Cette technique "bash friendly" sera utile lorsque flock est non disponible ou non applicable.

Un petit exemple

if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
    exit 1  # the global.lock already exists
fi

# ... remainder of script ...

Un exemple plus long

Cet exemple va attendre que le global.lock mais le délai d'attente est trop long.

 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir time_max=7140  # 7140 s = 1 hour 59 min.

    # poll for lock file up to ${time_max}s
    # put debugging info in lock file in case of issues ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
            echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
            return 1
        fi
        sleep 1
    done

    return 0
 }

 function lockfile_release()
 {
    rm -f /tmp/global.lock
 }

 if ! lockfile_waithold ; then
      exit 1
 fi
 trap lockfile_release EXIT

 # ... remainder of script ...

Cette technique a fonctionné de manière fiable pour moi sur un hôte Ubuntu 16 de longue date. L'hôte mettait régulièrement en file d'attente de nombreuses instances d'un bash script qui coordonnait le travail en utilisant le même fichier "lock" unique à l'échelle du système.

(Ceci est similaire à ce poste par @Barry Kelly qui a été remarqué par la suite).

4voto

martin clayton Points 41306

Je ne suis pas sûr qu'il existe une solution unique et robuste, donc vous pourriez finir par vous débrouiller tout seul.

Les fichiers de verrouillage sont imparfaits, mais moins que l'utilisation des pipelines 'ps | grep | grep -v'.

Cela dit, vous pourriez envisager de garder le contrôle du processus séparé de votre script - avoir un script de départ. Ou, au moins, le factoriser en fonctions tenues dans un fichier séparé, ainsi vous pourriez dans l'appelant script avoir :

. my_script_control.ksh

# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15

dans chaque script qui a besoin de la logique de contrôle. Le site piège s'assure que le fichier de verrouillage est supprimé lorsque l'appelant se retire, donc vous n'avez pas à coder ceci à chaque point de sortie dans le script.

L'utilisation d'un contrôle séparé script signifie que vous pouvez vérifier la sanité des cas limites : supprimer les fichiers journaux périmés, vérifier que le fichier de verrouillage est associé correctement à une instance en cours d'exécution du script, donner une option pour tuer le processus en cours d'exécution, et ainsi de suite. Cela signifie aussi que vous avez plus de chance d'utiliser grep sur ps avec succès. Un ps-grep peut être utilisé pour vérifier qu'un fichier lock est associé à un processus en cours d'exécution. Vous pourriez peut-être nommer vos fichiers lock d'une manière ou d'une autre pour inclure des informations sur le processus : utilisateur, pid, etc., qui peuvent être utilisées par une invocation ultérieure de script pour décider si le processus qui a créé le fichier de verrouillage est toujours en activité. qui a créé le fichier de verrouillage est toujours là.

4voto

James Tan Points 35

J'ai trouvé ceci dans les dépendances du paquet procmail :

apt install liblockfile-bin

Pour courir : dotlockfile -l file.lock

file.lock sera créé.

Pour déverrouiller : dotlockfile -u file.lock

Utilisez ceci pour lister les fichiers de ce paquet / commande : dpkg-query -L liblockfile-bin

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