Quelle est la méthode la plus simple pour vérifier qu’une seule instance d’un script Shell est en cours d’exécution à un moment donné?
Voici une implémentation qui utilise un lockfile et y renvoie un PID. Cela sert de protection si le processus est tué avant de supprimer le pidfile :
LOCKFILE=/tmp/lock.txt
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
echo "already running"
exit
fi
# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}
# do stuff
sleep 1000
rm -f ${LOCKFILE}
L'astuce ici est le kill -0
qui ne fournit aucun signal mais vérifie simplement si un processus avec le PID donné existe. De plus, l'appel à trap
garantira que le lockfile est supprimé même lorsque votre processus est tué (sauf kill -9
).
Utilisez flock(1)
pour créer un descripteur de fichier verrouillé exclusif. 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 compris entre (
et )
est exécuté uniquement par un processus à la fois et que le processus n’attend pas trop longtemps le verrouillage.
Avertissement: cette commande particulière fait partie de util-linux
. Si vous utilisez un système d'exploitation autre que Linux, il est possible que ce système ne soit pas disponible.
Toutes les approches qui testent l'existence de "fichiers de verrouillage" sont défectueuses.
Pourquoi? Parce qu'il n'y a aucun moyen de vérifier si un fichier existe et de le créer en une seule action atomique. À cause de ce; il existe une condition de concurrence critique pour que _ fasse échouer vos tentatives d'exclusion mutuelle.
Au lieu de cela, vous devez utiliser mkdir
. mkdir
crée un répertoire s'il n'existe pas encore et, le cas échéant, définit un code de sortie. Plus important encore, il fait tout cela en une seule action atomique, ce qui le rend parfait pour ce scénario.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Pour tous les détails, voir l'excellent BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Si vous voulez vous occuper des verrous périmés, fuser (1) est pratique. Le seul inconvénient est que l'opération prend environ une seconde et n'est donc pas instantanée.
Voici une fonction que j'ai écrite une fois qui résout le problème en utilisant fuser:
# mutex file
#
# Open a mutual exclusion lock on the file, unless another process already owns one.
#
# If the file is already locked by another process, the operation fails.
# This function defines a lock on a file as having a file descriptor open to the file.
# This function uses FD 9 to open a lock on the file. To release the lock, close FD 9:
# exec 9>&-
#
mutex() {
local file=$1 pid pids
exec 9>>"$file"
{ pids=$(fuser -f "$file"); } 2>&- 9>&-
for pid in $pids; do
[[ $pid = $$ ]] && continue
exec 9>&-
return 1 # Locked by a pid.
done
}
Vous pouvez l'utiliser dans un script comme ceci:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Si vous ne vous souciez pas de la portabilité (ces solutions devraient fonctionner sur à peu près n'importe quel système UNIX), Linux ' fuser (1) offre quelques options supplémentaires et il existe aussi flock (1) .
Il y a un wrapper autour de l'appel système flock (2) appelé, sans imagination, flock (1). Il est donc relativement facile d’obtenir de manière fiable des verrous exclusifs sans s’inquiéter du nettoyage, etc. Il existe des exemples dans la page de manuel concernant son utilisation dans un script Shell.
Vous avez besoin d'une opération atomique, comme flock, sinon cela finira par échouer.
Mais que faire si troupeau n'est pas disponible. Eh bien il y a mkdir. C'est une opération atomique aussi. Un seul processus aboutira à un mkdir réussi, tous les autres échoueront.
Le code est donc:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Vous devez vous occuper des verrous périmés, sinon un crash de votre script ne sera plus jamais exécuté.
Pour rendre le verrouillage fiable, vous avez besoin d’une opération atomique. Beaucoup des propositions ci-dessus Ne sont pas atomiques. L’utilitaire lockfile (1) proposé semble prometteur, comme l’indique la page de manuel Si votre système d'exploitation ne prend pas en charge lockfile (1) et Votre solution doit fonctionner sur NFS, vous n'avez pas beaucoup d'options ....
NFSv2 a deux opérations atomiques:
Avec NFSv3, l'appel de création est également atomique.
Les opérations d'annuaire ne sont PAS atomiques sous NFSv2 et NFSv3 (veuillez vous reporter au livre "NFS Illustrated" de Brent Callaghan, ISBN 0-201-32570-5; Brent est un vétéran de NFS chez Sun).
Sachant cela, vous pouvez implémenter des verrous pivotants pour les fichiers et les répertoires (dans Shell, pas PHP):
verrouiller le répertoire actuel:
while ! ln -s . lock; do :; done
verrouiller un fichier:
while ! ln -s ${f} ${f}.lock; do :; done
unlock dir actuel (hypothèse, le processus en cours a réellement acquis le verrou):
mv lock deleteme && rm deleteme
déverrouiller un fichier (par hypothèse, le processus en cours a réellement acquis le verrou):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Remove n’est pas non plus atomique, c’est pourquoi il faut d’abord renommer (qui est atomique) puis supprimer.
Pour les appels de lien symbolique et de renommage, les deux noms de fichiers doivent résider sur le même système de fichiers. Ma proposition: n'utilisez que des noms de fichiers simples (pas de chemins) et placez fichier et verrouillez-le dans le même répertoire.
Une autre option consiste à utiliser l'option noclobber
de Shell en exécutant set -C
. Alors >
échouera si le fichier existe déjà.
En bref:
set -C
lockfile="/tmp/locktest.lock"
if echo "$$" > "$lockfile"; then
echo "Successfully acquired lock"
# do work
rm "$lockfile" # XXX or via trap - see below
else
echo "Cannot acquire lock - already locked by $(cat "$lockfile")"
fi
Cela provoque l'appel du shell:
open(pathname, O_CREAT|O_EXCL)
qui crée le fichier de manière atomique ou échoue si le fichier existe déjà.
Selon un commentaire sur BashFAQ 045 , cela peut échouer dans ksh88
, mais cela fonctionne dans tous mes shells:
$ strace -e trace=creat,open -f /bin/bash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3
$ strace -e trace=creat,open -f /bin/zsh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_LARGEFILE, 0666) = 3
$ strace -e trace=creat,open -f /bin/pdksh /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666) = 3
$ strace -e trace=creat,open -f /bin/dash /home/mikel/bin/testopen 2>&1 | grep -F testopen.lock
open("/tmp/testopen.lock", O_WRONLY|O_CREAT|O_EXCL|O_LARGEFILE, 0666) = 3
Il est intéressant de noter que pdksh
ajoute le drapeau O_TRUNC
, mais il est évidemment redondant:
soit vous créez un fichier vide, soit vous ne faites rien.
La manière dont vous faites rm
dépend de la manière dont vous voulez que les sorties non nettoyées soient gérées.
Supprimer en sortie propre
Les nouvelles exécutions échouent jusqu'à ce que le problème à l'origine de la résolution de la sortie non nettoyée et la suppression manuelle du fichier de verrouillage soient supprimés.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Supprimer à n'importe quelle sortie
Les nouvelles exécutions réussissent à condition que le script ne soit pas déjà en cours d'exécution.
trap 'rm "$lockfile"' EXIT
Pour les scripts Shell, j’ai tendance à utiliser mkdir
à flock
car cela rend les verrous plus portables.
De toute façon, utiliser set -e
ne suffit pas. Cela ne ferme le script que si une commande échoue. Vos serrures seront toujours laissés.
Pour un nettoyage correct des verrous, vous devez définir vos interruptions sur quelque chose comme ce code psuedo (levé, simplifié et non testé mais à partir 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 arriver. Tous les pièges produiront une sortie afin que la fonction __sig_exit
soit toujours exécutée (sauf un SIGKILL) qui nettoie vos verrous.
Remarque: mes valeurs de sortie ne sont pas des valeurs basses. Pourquoi? Différents systèmes de traitement par lots établissent ou ont des attentes pour les nombres compris entre 0 et 31. En leur attribuant une autre valeur, je peux faire en sorte que mes scripts et les flux de traitement par lots réagissent en fonction du travail par lots ou du script précédent.
Vous pouvez utiliser GNU Parallel
pour cela car il fonctionne comme un mutex lorsqu'il est appelé sem
. Donc, concrètement, vous pouvez utiliser:
sem --id SCRIPTSINGLETON yourScript
Si vous voulez aussi un délai d'attente, utilisez:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Un délai d'expiration inférieur à 0 signifie que vous quittez sans exécuter le script si le sémaphore n'est pas libéré dans le délai imparti, un délai d'expiration supérieur à 0 signifie que le script est toujours exécuté.
Notez que vous devriez lui donner un nom (avec --id
), sinon le terminal de contrôle le paramétrera par défaut.
GNU Parallel
est une installation très simple sur la plupart des plates-formes Linux/OSX/Unix - il ne s'agit que d'un script Perl.
Vraiment rapide et vraiment sale? Cette ligne sur le dessus de votre script fonctionnera:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Bien sûr, assurez-vous que votre nom de script est unique. :)
Cet exemple est expliqué dans man flock, mais il nécessite quelques améliorations car nous devrions gérer les bugs et les codes de sortie:
#!/bin/bash
#set -e this is useful 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" ]; then 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, la liste des processus que j'ai utilisés dans le passé. Mais c'est plus compliqué que la méthode ci-dessus. Vous devriez lister les processus par ps, filtrer par son nom, filtre supplémentaire grep -v grep pour supprimer le parasite et enfin le compter par grep -c. et comparer avec le nombre. C'est compliqué et incertain
Créer un fichier de verrouillage dans un emplacement connu et vérifier son existence au démarrage du script? Inscrire le PID dans le fichier peut être utile si quelqu'un tente de localiser une instance erronée empêchant l'exécution du script.
Voici une approche qui combine le verrouillage de répertoire atomique avec une vérification du verrouillage obsolète via PID et un redémarrage si obsolète. En outre, cela ne repose sur aucun basisme.
#!/bin/dash
SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"
if ! mkdir $LOCKDIR 2>/dev/null
then
# lock failed, but check for stale one by checking if the PID is really existing
PID=$(cat $PIDFILE)
if ! kill -0 $PID 2>/dev/null
then
echo "Removing stale lock of nonexistent PID ${PID}" >&2
rm -rf $LOCKDIR
echo "Restarting myself (${SCRIPTNAME})" >&2
exec "$0" "$@"
fi
echo "$SCRIPTNAME is already running, bailing out" >&2
exit 1
else
# lock successfully acquired, save PID
echo $$ > $PIDFILE
fi
trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT
echo hello
sleep 30s
echo bye
Si les limitations de flock, qui ont déjà été décrites ailleurs dans ce fil, ne vous concernent pas, cela devrait fonctionner:
#!/bin/bash
{
# exit if we are unable to obtain a lock; this would happen if
# the script is already running elsewhere
# note: -x (exclusive) is the default
flock -n 100 || exit
# put commands to run here
sleep 100
} 100>/tmp/myjob.lock
Lorsque je cible une machine Debian, le paquet lockfile-progs
est une bonne solution. procmail
est également livré avec un outil lockfile
. Cependant parfois je suis coincé avec ni l'un ni l'autre.
Voici ma solution qui utilise mkdir
pour atomic-ness et un fichier PID pour détecter les verrous périmés. Ce code est actuellement en production sur une configuration Cygwin et fonctionne bien.
Pour l'utiliser, appelez simplement exclusive_lock_require
lorsque vous avez besoin d'un accès exclusif à quelque chose. Un paramètre facultatif de nom de verrou vous permet de partager des verrous entre différents scripts. Il existe également deux fonctions de niveau inférieur (exclusive_lock_try
et exclusive_lock_retry
) si vous avez besoin de quelque chose de plus complexe.
function exclusive_lock_try() # [lockname]
{
local LOCK_NAME="${1:-`basename $0`}"
LOCK_DIR="/tmp/.${LOCK_NAME}.lock"
local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"
if [ -e "$LOCK_DIR" ]
then
local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
if [ ! -z "$LOCK_PID" ] && kill -0 "$LOCK_PID" 2> /dev/null
then
# locked by non-dead process
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
return 1
else
# orphaned lock, take it over
( echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null && local LOCK_PID="$$"
fi
fi
if [ "`trap -p EXIT`" != "" ]
then
# already have an EXIT trap
echo "Cannot get lock, already have an EXIT trap"
return 1
fi
if [ "$LOCK_PID" != "$$" ] &&
! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
then
local LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"
# unable to acquire lock, new process got in first
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"
return 1
fi
trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT
return 0 # got lock
}
function exclusive_lock_retry() # [lockname] [retries] [delay]
{
local LOCK_NAME="$1"
local MAX_TRIES="${2:-5}"
local DELAY="${3:-2}"
local TRIES=0
local LOCK_RETVAL
while [ "$TRIES" -lt "$MAX_TRIES" ]
do
if [ "$TRIES" -gt 0 ]
then
sleep "$DELAY"
fi
local TRIES=$(( $TRIES + 1 ))
if [ "$TRIES" -lt "$MAX_TRIES" ]
then
exclusive_lock_try "$LOCK_NAME" > /dev/null
else
exclusive_lock_try "$LOCK_NAME"
fi
LOCK_RETVAL="${PIPESTATUS[0]}"
if [ "$LOCK_RETVAL" -eq 0 ]
then
return 0
fi
done
return "$LOCK_RETVAL"
}
function exclusive_lock_require() # [lockname] [retries] [delay]
{
if ! exclusive_lock_retry "$@"
then
exit 1
fi
}
Je voulais supprimer les fichiers de verrouillage, les répertoires de verrouillage, les programmes de verrouillage spéciaux et même pidof
, car ils ne sont pas présents sur toutes les installations Linux. Voulais aussi avoir le code le plus simple possible (ou au moins le moins de lignes possible). Déclaration la plus simple if
, sur une ligne:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
Certains unixes ontlockfile
, ce qui est très similaire auflock
déjà mentionné.
De la page de manuel:
lockfile peut être utilisé pour créer un ou plusieurs fichiers sémaphores. Si lock - Le fichier ne peut pas créer tous les fichiers .__ spécifiés. fichiers (dans l’ordre spécifié), il attend le sommeil (valeur par défaut de 8) secondes et réessaie le dernier fichier que n'a pas réussi. Vous pouvez spécifier le nombre de tentatives à faire jusqu'à l'échec est retourné. Si le nombre de tentatives est -1 (par défaut, c'est-à-dire, -r-1), lockfile réessayera pour toujours.
Les réponses existantes publiées reposent sur l'utilitaire CLI flock
ou ne sécurisent pas correctement le fichier de verrouillage. L'utilitaire flock n'est pas disponible sur tous les systèmes non Linux (à savoir, FreeBSD) et ne fonctionne pas correctement sur NFS.
À mes débuts en administration système et en développement système, on m'a dit qu'une méthode sûre et relativement portable de création d'un fichier de verrouillage consistait à créer un fichier temporaire à l'aide de mkemp(3)
ou mkemp(1)
, à écrire des informations d'identification dans le fichier temporaire (c.-à-d. PID), puis lier durement le fichier temporaire au fichier verrou. Si le lien a réussi, vous avez réussi à obtenir le verrou.
Lorsque vous utilisez des verrous dans les scripts Shell, je place généralement une fonction obtain_lock()
dans un profil partagé, puis la source à partir des scripts. Voici un exemple de ma fonction de verrouillage:
obtain_lock()
{
LOCK="${1}"
LOCKDIR="$(dirname "${LOCK}")"
LOCKFILE="$(basename "${LOCK}")"
# create temp lock file
TMPLOCK=$(mktemp -p "${LOCKDIR}" "${LOCKFILE}XXXXXX" 2> /dev/null)
if test "x${TMPLOCK}" == "x";then
echo "unable to create temporary file with mktemp" 1>&2
return 1
fi
echo "$$" > "${TMPLOCK}"
# attempt to obtain lock file
ln "${TMPLOCK}" "${LOCK}" 2> /dev/null
if test $? -ne 0;then
rm -f "${TMPLOCK}"
echo "unable to obtain lockfile" 1>&2
if test -f "${LOCK}";then
echo "current lock information held by: $(cat "${LOCK}")" 1>&2
fi
return 2
fi
rm -f "${TMPLOCK}"
return 0;
};
Voici un exemple d'utilisation de la fonction de verrouillage:
#!/bin/sh
. /path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"
clean_up()
{
rm -f "${PROG_LOCKFILE}"
}
obtain_lock "${PROG_LOCKFILE}"
if test $? -ne 0;then
exit 1
fi
trap clean_up SIGHUP SIGINT SIGTERM
# bulk of script
clean_up
exit 0
# end of script
N'oubliez pas d'appeler clean_up
à tout point de sortie de votre script.
J'ai utilisé ce qui précède dans les environnements Linux et FreeBSD.
Les PID et lockfiles sont certainement les plus fiables. Lorsque vous essayez d'exécuter le programme, il peut vérifier le fichier verrou qui, s'il existe, peut utiliser ps
pour voir si le processus est toujours en cours d'exécution. Si ce n'est pas le cas, le script peut démarrer en mettant à jour le PID du fichier de verrouillage.
En fait, bien que la réponse de bmdhacks soit presque bonne, il existe une faible chance que le second script s'exécute après la première vérification du fichier verrou et avant son écriture. Ainsi, ils écriront tous les deux le fichier de verrouillage et s'exécuteront tous les deux. Voici comment le faire fonctionner à coup sûr:
lockfile=/var/lock/myscript.lock
if ( set -o noclobber; echo "$$" > "$lockfile") 2> /dev/null ; then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else
# or you can decide to skip the "else" part if you want
echo "Another instance is already running!"
fi
L'option noclobber
s'assurera que la commande de redirection échouera si le fichier existe déjà. Donc, la commande de redirection est réellement atomique - vous écrivez et vérifiez le fichier avec une seule commande. Vous n'avez pas besoin de supprimer le fichier de verrouillage à la fin du fichier - il sera supprimé par le piège. J'espère que cela aidera les gens qui le liront plus tard.
P.S. Je n'ai pas vu que Mikel avait déjà répondu correctement à la question, bien qu'il n'ait pas inclus la commande trap afin de réduire les chances que le fichier de verrouillage reste après l'arrêt du script avec Ctrl-C par exemple. C'est donc la solution complète
J'utilise une approche simple qui gère les fichiers de verrouillage obsolètes.
Notez que certaines des solutions ci-dessus qui stockent le pid ignorent le fait que le pid peut s’enrouler. Donc, il ne suffit pas de vérifier s’il existe un processus valide avec le pid stocké, en particulier pour les scripts de longue durée.
J'utilise noclobber pour m'assurer qu'un seul script peut ouvrir et écrire dans le fichier de verrouillage à la fois. En outre, je stocke suffisamment d'informations pour identifier de manière unique un processus dans le fichier verrou. Je définis l'ensemble de données pour identifier de manière unique un processus pid, ppid, lstart.
Lorsqu'un nouveau script démarre, s'il ne parvient pas à créer le fichier de verrouillage, il vérifie que le processus qui a créé le fichier de verrouillage est toujours actif. Sinon, nous supposons que le processus original est mort d'une mort sans grâce et a laissé un fichier de verrouillage périmé. Le nouveau script prend alors possession du fichier de verrouillage, et tout va bien dans le monde, encore une fois.
Devrait fonctionner avec plusieurs coques sur plusieurs plates-formes. Rapide, portable et simple.
#!/usr/bin/env sh
# Author: rouble
LOCKFILE=/var/tmp/lockfile #customize this line
trap release INT TERM EXIT
# Creates a lockfile. Sets global variable $ACQUIRED to true on success.
#
# Returns 0 if it is successfully able to create lockfile.
acquire () {
set -C #Shell noclobber option. If file exists, > will fail.
UUID=`ps -eo pid,ppid,lstart $$ | tail -1`
if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
ACQUIRED="TRUE"
return 0
else
if [ -e $LOCKFILE ]; then
# We may be dealing with a stale lock file.
# Bring out the magnifying glass.
CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`
if [ "$CURRENT_UUID_FROM_LOCKFILE" == "$CURRENT_UUID_FROM_PS" ]; then
echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE" >&2
return 1
else
# The process that created this lock file died an ungraceful death.
# Take ownership of the lock file.
echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
release "FORCE"
if (echo "$UUID" > "$LOCKFILE") 2>/dev/null; then
ACQUIRED="TRUE"
return 0
else
echo "Cannot write to $LOCKFILE. Error." >&2
return 1
fi
fi
else
echo "Do you have write permissons to $LOCKFILE ?" >&2
return 1
fi
fi
}
# Removes the lock file only if this script created it ($ACQUIRED is set),
# OR, if we are removing a stale lock file (first parameter is "FORCE")
release () {
#Destroy lock file. Take no prisoners.
if [ "$ACQUIRED" ] || [ "$1" == "FORCE" ]; then
rm -f $LOCKFILE
fi
}
# Test code
# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if [ $? -eq 0 ]; then
echo "Acquired lock."
read -p "Press [Enter] key to release lock..."
release
echo "Released lock."
else
echo "Unable to acquire lock."
fi
Un exemple avec flock (1) mais sans sous-shell. Le fichier flock () ed/tmp/foo n'est jamais supprimé, mais cela n'a pas d'importance car il obtient flock () et un-flock () ed.
#!/bin/bash
exec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; then
echo "lock failed, exiting"
exit
fi
#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&- #close fd 9, and release lock
#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5
Ajoutez simplement [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
au début de votre script. C'est un code passe-partout de man flock . Pour comprendre son fonctionnement, j'ai écrit un script et l'ai exécuté simultanément à partir de deux consoles:
#!/bin/bash
if [ "${FLOCKER}" != "$0" ]; then
echo "FLOCKER=$FLOCKER \$0=$0 ($$)"
exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
else
echo "FLOCKER equals \$0 = $FLOCKER ($$)"
fi
sleep 10
echo "Process $$ finished"
Je n'ai pas encore pleinement compris son fonctionnement, mais il semble qu'il se remette à fonctionner en utilisant lui-même un fichier de verrouillage. FLOCKER
défini sur "$0"
juste pour définir une valeur raisonnable non négligeable. || :
ne rien faire en cas de problème.
Cela ne semble pas fonctionner sous Debian 7, mais semble fonctionner de nouveau avec le paquet expérimental util-linux 2.25. Il écrit "flock: ... Fichier texte occupé". Il pourrait être remplacé en désactivant l'autorisation d'écriture sur votre script.
L'utilisation du verrou du processus est beaucoup plus forte et prend en charge les sorties inusitées également . Lock_file est maintenu ouvert aussi longtemps que le processus est en cours d'exécution. Il sera fermé (par Shell) une fois que le processus existe (même s'il est tué). J'ai trouvé cela très efficace:
lock_file=/tmp/`basename $0`.lock
if fuser $lock_file > /dev/null 2>&1; then
echo "WARNING: Other instance of $(basename $0) running."
exit 1
fi
exec 3> $lock_file
Je trouve que la solution de bmdhack est la plus pratique, du moins pour mon cas d'utilisation. L'utilisation de flock et de lockfile repose sur la suppression du fichier lockfile à l'aide de rm lorsque le script se termine, ce qui ne peut pas toujours être garanti (par exemple, kill -9).
Je voudrais changer une chose mineure à propos de la solution de bmdhack: elle supprime le fichier de verrouillage sans indiquer que cela est inutile pour le fonctionnement en toute sécurité de ce sémaphore. Son utilisation de kill -0 garantit qu'un ancien fichier de verrouillage pour un processus inactif sera simplement ignoré/écrasé.
Ma solution simplifiée consiste donc simplement à ajouter les éléments suivants en haut de votre singleton:
## Test the lock
LOCKFILE=/tmp/singleton.lock
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; then
echo "Script already running. bye!"
exit
fi
## Set the lock
echo $$ > ${LOCKFILE}
Bien sûr, ce script a toujours le défaut que les processus susceptibles de démarrer en même temps présentent un risque de concurrence critique, car le test de verrouillage et les opérations de définition ne constituent pas une action atomique unique. Mais la solution proposée par lhunath pour utiliser mkdir a la faille qu'un script tué peut laisser derrière le répertoire, empêchant ainsi d'autres instances de s'exécuter.
L'utilitaire sémaphorique utilise flock
(comme indiqué ci-dessus, par exemple par presto8) pour implémenter un sémaphore de comptage Il permet d'activer un nombre spécifique de processus simultanés. Nous l'utilisons pour limiter le niveau de simultanéité de divers processus de travail en file d'attente.
C'est comme sem mais beaucoup plus léger. (Divulgation complète: je l'ai écrit après avoir constaté que le sem était trop lourd pour nos besoins et qu'il n'existait pas d'utilitaire de comptage simple.)
Répondu déjà un million de fois, mais d'une autre manière, sans nécessité de dépendances externes:
LOCK_FILE="/var/lock/$(basename "$0").pid"
trap "rm -f ${LOCK_FILE}; exit" INT TERM EXIT
if [[ -f $LOCK_FILE && -d /proc/`cat $LOCK_FILE` ]]; then
// Process already exists
exit 1
fi
echo $$ > $LOCK_FILE
Chaque fois qu'il écrit le PID actuel ($$) dans le fichier de verrouillage, le démarrage du script vérifie si un processus est en cours d'exécution avec le dernier PID.
Je ne l’ai trouvé mentionné nulle part, il utilise read, je ne sais pas exactement si read est réellement atomique mais il m’a bien servi jusqu’à présent ..., c’est juteux parce que ce n’est que des basins intégrés, c’est un processus en cours. Lors de la mise en œuvre, vous démarrez le coprocess de locker et utilisez son e/s pour gérer les verrous. De la même manière, vous pouvez le faire interprocessus en échangeant simplement l'entrée/sortie cible des descripteurs de fichier locker par un descripteur de fichier système (exec 3<>/file && exec 4</file
)
## gives locks
locker() {
locked=false
while read l; do
case "$l" in
lock)
if $locked; then
echo false
else
locked=true
echo true
fi
;;
unlock)
if $locked; then
locked=false
echo true
else
echo false
fi
;;
*)
echo false
;;
esac
done
}
## locks
lock() {
local response
echo lock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
## unlocks
unlock() {
local response
echo unlock >&${locker[1]}
read -ru ${locker[0]} response
$response && return 0 || return 1
}
Le chemin de troupeau est le chemin à parcourir. Pensez à ce qui se passe lorsque le script meurt subitement. Dans le cas d'un troupeau, vous perdez simplement le troupeau, mais ce n'est pas un problème. Notez également qu’un truc diabolique est de prendre un troupeau sur le script lui-même. Mais cela vous permet bien sûr de lancer à la va-vite les problèmes d’autorisation.
pourquoi ne pas utiliser quelque chose comme
pgrep -f $cmd || $cmd
Rapide et sale?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile
Jetez un coup d’œil à FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/ : vous pouvez synchroniser des commandes et/ou des scripts à l’aide de ressources abstraites ne nécessitant pas le verrouillage de fichiers dans un système de fichiers. Vous pouvez synchroniser des commandes exécutées sur différents systèmes sans un NAS (stockage associé au réseau), contrairement à un serveur NFS (Network File System).
En utilisant le cas d'utilisation le plus simple, la sérialisation de "commande1" et "commande2" peut être aussi simple que d'exécuter:
flom -- command1
et
flom -- command2
à partir de deux scripts shell différents.
if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then
exit 1
fi
Cette réponse à une ligne provient d'une personne proche Demandez à Ubuntu Q & A :
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
# This is useful boilerplate code for Shell scripts. Put it at the top of
# the Shell script you want to lock and it'll automatically lock itself on
# the first run. If the env var $FLOCKER is not set to the Shell script
# that is being run, then execute flock and grab an exclusive non-blocking
# lock (using the script itself as the lock file) before re-execing itself
# with the right arguments. It also sets the FLOCKER env var to the right
# value so it doesn't run again.
J'ai une solution simple basée sur le nom du fichier
#!/bin/bash
MY_FILENAME=`basename "$BASH_SOURCE"`
MY_PROCESS_COUNT=$(ps a -o pid,cmd | grep $MY_FILENAME | grep -v grep | grep -v $$ | wc -
l)
if [ $MY_PROCESS_COUNT -ne 0 ]; then
echo found another process
exit 0
if
# Follows the code to get the job done.
Voici un plus élégant, sûr, rapide & sale méthode, en combinant les réponses fournies ci-dessus.
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
}
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
Vous pouvez également utiliser ceci: https://github.com/sayanarijit/pidlock
Sudo pip install -U pidlock
pidlock -n sleepy_script -c 'sleep 10'
Tard à la fête, utilisant l’idée de @Majal, c’est mon script pour ne démarrer qu’une seule instance de l'interface graphique emacsclient. Grâce à cela, je peux configurer raccourci clavier pour ouvrir ou revenir au même client emacs. J'ai un autre script pour appeler emacsclient dans les terminaux lorsque j'en ai besoin. L'utilisation de emacsclient ici est juste pour montrer un exemple de travail, on peut choisir autre chose. Cette approche est rapide et suffisante pour mes scripts minuscules. Dis-moi où c'est sale :)
#!/bin/bash
# if [ $(pgrep -c $(basename $0)) -lt 2 ]; then # this works but requires script name to be unique
if [ $(pidof -x "$0"|wc -w ) -lt 3 ]; then
echo -e "Starting $(basename $0)"
emacsclient --alternate-editor="" -c "$@"
else
echo -e "$0 is running already"
fi