web-dev-qa-db-fra.com

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 meilleur de s’assurer que seulement une instance d’un script est en cours d’exécution - en supposant que ce soit Bash sous Linux?

En ce moment je fais:

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

mais il a plusieurs problèmes:

  1. il met le chèque en dehors du script
  2. cela ne me permet pas d'exécuter le même script à partir de comptes distincts - 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 de pidfile, mais je sens qu’il devrait exister un moyen simple de le faire.

96
user80168

Si le script est identique pour tous les utilisateurs, vous pouvez utiliser une approche lockfile. Si vous obtenez le verrou, continuez sinon afficher un message et quitter.

Par exemple:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $ 

Après /tmp/the.lock a été acquis, votre script sera le seul à avoir accès à l'exécution. Lorsque vous avez terminé, enlevez le verrou. Sous forme de script, cela pourrait ressembler à:

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock
96
ezpz

Le verrouillage consultatif a été utilisé pendant des siècles et il peut être utilisé dans des scripts bash. Je préfère les simples flock (de util-linux[-ng]) sur lockfile (de procmail). Et souvenez-vous toujours d’un piège à la sortie (sigspec == EXIT ou 0, piéger des signaux spécifiques est superflu) dans ces scripts.

En 2009, j'ai publié mon script passe-partout verrouillable (disponible à l'origine sur ma page wiki, aujourd'hui sous la forme Gist ). Transformer cela en une instance par utilisateur est simple. En l'utilisant, vous pouvez également écrire facilement des scripts pour d'autres scénarios nécessitant un verrouillage ou une synchronisation.

Voici le passe-partout mentionné pour votre commodité.

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

## Copyright (C) 2009 Przemyslaw Pawelczyk <[email protected]>
##
## 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.
136
przemoc

Je pense que flock est probablement la variante la plus facile (et la plus mémorable). Je l'utilise dans un travail cron pour encoder automatiquement dvds et cds

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

Utilisation -w pour les délais d'expiration ou les options d'exclusion d'attendre le relâchement du verrouillage. Enfin, la page de manuel montre un bel exemple pour plusieurs commandes:

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

Utilisez le set -o noclobber _ et essayez de remplacer un fichier commun.

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 attendra le global.lock fichier mais expiration après trop longtemps.

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


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

7
JamesThomasMoon1979

Je ne suis pas sûr qu'il existe une solution robuste en une ligne, de sorte que vous pourriez vous retrouver vous-même.

Les fichiers de verrouillage sont imparfaits, mais moins que d'utiliser 'ps | grep | pipeline de grep -v '.

Cela dit, vous pouvez envisager de séparer le contrôle de processus de votre script: créez un script de démarrage. Ou, du moins, intégrez-le aux fonctions détenues dans un fichier séparé, de sorte que, dans le script de l'appelant, vous pourriez 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 trap garantit que le fichier de verrouillage est supprimé lorsque l'appelant quitte. Vous n'avez donc pas à le coder à chaque point de sortie du script.

L'utilisation d'un script de contrôle distinct signifie que vous pouvez vérifier l'intégrité des cas Edge: supprimez les fichiers journaux obsolètes, vérifiez que le fichier de verrouillage est correctement associé à une instance du script en cours d'exécution, donnez la possibilité de tuer le processus en cours d'exécution, etc. Cela signifie également que vous avez une meilleure chance d'utiliser grep sur la sortie ps. Un ps-grep peut être utilisé pour vérifier qu'un fichier en cours est associé à un processus en cours d'exécution. Vous pourriez peut-être nommer vos fichiers de verrouillage de manière à inclure des informations sur le processus: utilisateur, pid, etc., qui pourront être utilisées par une invocation de script ultérieure pour décider si le processus qui a créé le fichier de verrouillage existe toujours.

3
martin clayton

premier exemple de test

[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"

deuxième exemple de test

currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
  sleep 11111111111111111
else
  echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n"
  false
  exit 1
fi

explication

"lsof -t" pour lister tous les pids des scripts en cours d'exécution nommés "$ 0".

Commande "lsof" fera deux avantages.

  1. Ignorez les pids édités par l'éditeur tel que vim, car vim édite son fichier de mappage tel que ".file.swp".
  2. Ignore les pids créés par les scripts Shell en cours d'exécution, ce que la plupart des commandes dérivées "grep" ne peuvent pas atteindre. Utilisez la commande "pstree -pH pidnum" pour afficher des détails sur l'état du processus en cours.
2
yanyingwang

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

apt install liblockfile-bin

Courir: dotlockfile -l file.lock

file.lock sera créé.

Déverouiller: dotlockfile -u file.lock

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

2
James Tan

Les distributions Ubuntu/Debian ont le start-stop-daemon outil qui a le même objectif que vous le décrivez. Voir aussi / etc/init.d/skeleton pour voir comment il est utilisé pour écrire les scripts de démarrage/d'arrêt.

- Noé

1
Noah Spurrier

Une solution ultime en ligne:

[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"
1
Dm1

Je vous recommande également de regarder chpst (composant de runit):

chpst -L /tmp/your-lockfile.loc ./script.name.sh
1
rud

J'ai eu le même problème, et est venu avec un modèle qui utilise lockfile, un fichier pid qui contient le numéro d'identification du processus, et un kill -0 $(cat $pid_file) vérifier que les scripts abandonnés n'arrêtent pas le prochaine course. Cela crée un dossier foobar- $ USERID dans/tmp où vivent les fichiers lockfile et pid.

Vous pouvez toujours appeler le script et faire d'autres choses, tant que vous conservez ces actions dans alertRunningPS.

#!/bin/bash

user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$

function alertRunningPS () {
    local PID=$(cat "$pid_file" 2> /dev/null)
    echo "Lockfile present. ps id file: $PID"
    echo "Checking if process is actually running or something left over from crash..."
    if kill -0 $PID 2> /dev/null; then
        echo "Already running, exiting"
        exit 1
    else
        echo "Not running, removing lock and continuing"
        rm -f "$lock_file"
        lockfile -r 0 "$lock_file"
    fi
}

echo "Hello, checking some stuff before locking stuff"

# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS

# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"

sleep 30s

rm -f "$lock_file"
rm -f "$pid_file"
exit 0
0
Erik Lundmark