Voici une implémentation qui utilise un fichier de verrouillage et y fait écho 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}`; thenecho"already running"exitfi# make sure the lockfile is removed when we exit and then claim ittrap"rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}# do stuff
sleep 1000
rm -f ${LOCKFILE}
L'astuce ici est celle kill -0qui ne délivre aucun signal mais vérifie simplement si un processus avec le PID donné existe. L'appel à trapgarantira également que le fichier de verrouillage est supprimé même lorsque votre processus est tué (sauf kill -9).
Comme déjà mentionné dans un commentaire sur une autre réponse, cela a un défaut fatal - si l'autre script démarre entre la vérification et l'écho, vous êtes grillé.
Paul Tomblin
1
L'astuce du lien symbolique est intéressante, mais si le propriétaire du fichier de verrouillage est kill -9'd ou que le système plante, il y a toujours une condition de concurrence pour lire le lien symbolique, remarquer que le propriétaire est parti, puis le supprimer. Je m'en tiens à ma solution.
bmdhacks
10
Le contrôle et la création atomiques sont disponibles dans le shell en utilisant flock (1) ou lockfile (1). Voir d'autres réponses.
dmckee --- ex-moderator chaton
3
Voir ma réponse pour un moyen portable de faire une vérification atomique et de créer sans avoir à compter sur des utilitaires tels que flock ou lockfile.
lhunath
3
Ce n'est pas atomique et donc inutile. Vous avez besoin d'un mécanisme atomique pour tester et définir.
K Richard Pixley
219
Utilisez flock(1)pour faire d'un verrou à portée exclusive un 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 (et )n'est exécuté que par un processus à la fois et que le processus n'attend pas trop longtemps un verrou.
Attention: cette commande particulière fait partie de util-linux. Si vous exécutez un système d'exploitation autre que Linux, il peut être disponible ou non.
Qu'est-ce que le 200? Il dit «fd» dans le manul, mais je ne sais pas ce que cela signifie.
chovy
4
@chovy "descripteur de fichier", un descripteur entier désignant un fichier ouvert.
Alex B
6
Si quelqu'un d'autre se demande: la syntaxe ( command A ) command Binvoque un sous-shell pour command A. Documenté sur tldp.org/LDP/abs/html/subshells.html . Je ne suis toujours pas sûr du moment de l'invocation du sous-shell et de la commande B.
Dr Jan-Philip Gehrcke
1
Je pense que le code à l'intérieur du sous-shell devrait ressembler davantage à: de if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fisorte que si le délai d'attente se produit (un autre processus a le fichier verrouillé), ce script ne continue pas et ne modifie pas le fichier. Probablement ... le contre-argument est `` mais si cela a pris 10 secondes et que le verrou n'est toujours pas disponible, il ne sera jamais disponible '', probablement parce que le processus qui maintient le verrou ne se termine pas (peut-être est-il en cours d'exécution sous un débogueur?).
Jonathan Leffler
1
Le fichier redirigé vers n'est qu'un dossier de réservation sur lequel le verrou doit agir, il n'y a pas de données significatives. Le exitest de la partie à l'intérieur du (). Lorsque le sous-processus se termine, le verrou est automatiquement libéré, car aucun processus ne le retient.
clacke
160
Toutes les approches qui testent l'existence de "fichiers verrouillés" 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 qui rendra vos tentatives d'exclusion mutuelle interrompues.
Au lieu de cela, vous devez utiliser mkdir. mkdircrée un répertoire s'il n'existe pas encore, et si c'est le cas, il 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; thenecho"Myscript is already running." >&2
exit 1
fi
Si vous souhaitez éliminer les verrous périmés, l' unité de fusion (1) est très pratique. Le seul inconvénient ici est que l'opération prend environ une seconde, donc ce n'est pas instantané.
Voici une fonction que j'ai écrite une fois qui résout le problème d'utilisation de l'unité de fusion:
# 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 = $$ ]] && continueexec 9>&-
return 1 # Locked by a pid.done
}
Si vous ne vous souciez pas de la portabilité (ces solutions devraient fonctionner sur à peu près n'importe quel boîtier UNIX), le fuser de Linux (1) offre des options supplémentaires et il y a aussi flock (1) .
Vous pouvez combiner la if ! mkdirpartie en vérifiant si le processus avec le PID stocké (lors du démarrage réussi) à l'intérieur du lockdir est réellement en cours d'exécution et identique au script pour la protection stalenes. Cela protégerait également contre la réutilisation du PID après un redémarrage, et ne nécessiterait même pas fuser.
Tobias Kienzler
4
Il est certainement vrai que ce mkdirn'est pas défini comme une opération atomique et en tant que tel, «effet secondaire» est un détail d'implémentation du système de fichiers. Je le crois pleinement s'il dit que NFS ne l'implémente pas de manière atomique. Bien que je ne soupçonne pas que votre /tmpsera un partage NFS et sera probablement fourni par un fs qui implémente mkdiratomiquement.
lhunath
5
Mais il existe un moyen de vérifier l'existence d'un fichier régulier et de le créer de manière atomique si ce n'est pas le cas: en utilisant lnpour créer un lien physique à partir d'un autre fichier. Si vous avez des systèmes de fichiers étranges qui ne garantissent pas cela, vous pouvez vérifier l'inode du nouveau fichier par la suite pour voir s'il est le même que le fichier d'origine.
Juan Cespedes
4
Il existe «un moyen de vérifier si un fichier existe et de le créer en une seule action atomique» - c'est open(... O_CREAT|O_EXCL). Pour ce faire, vous avez juste besoin d'un programme utilisateur approprié, tel que lockfile-create(in lockfile-progs) ou dotlockfile(in liblockfile-bin). Et assurez-vous de nettoyer correctement (par exemple trap EXIT), ou testez les verrous périmés (par exemple avec --use-pid).
Toby Speight
5
"Toutes les approches qui testent l'existence de" fichiers verrouillés "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." - Pour le rendre atomique, il faut le faire à au niveau du noyau - et cela se fait au niveau du noyau avec flock (1) linux.die.net/man/1/flock qui, d'après la date de copyright de l'homme, existe depuis au moins 2006. J'ai donc fait un vote défavorable (- 1), rien de personnel, juste une forte conviction que l'utilisation des outils implémentés par le noyau fournis par les développeurs du noyau est correcte.
Craig Hicks
42
Il y a un wrapper autour de l'appel système flock (2) appelé, sans imagination, flock (1). Cela rend relativement facile l'obtention de verrous exclusifs de manière fiable sans se soucier du nettoyage, etc. Il y a des exemples sur la page de manuel pour savoir comment l'utiliser dans un script shell.
L' flock()appel système n'est pas POSIX et ne fonctionne pas pour les fichiers sur les montages NFS.
maxschlepzig
17
Exécution à partir d'un travail Cron que j'utilise flock -x -n %lock file% -c "%command%"pour m'assurer qu'une seule instance est en cours d'exécution.
Ryall
Aww, au lieu du troupeau sans imagination (1), ils auraient dû aller avec quelque chose comme le troupeau (U). .. .il a une certaine familiarité avec cela. . .semble que je l'ai entendu avant une fois ou deux.
Kent Kruckeberg
Il est à noter que la documentation de flock (2) spécifie une utilisation uniquement avec des fichiers, mais que la documentation de flock (1) spécifie une utilisation avec un fichier ou un répertoire. La documentation flock (1) n'est pas explicite sur la manière d'indiquer la différence lors de la création, mais je suppose que cela se fait en ajoutant un "/" final. Quoi qu'il en soit, si flock (1) peut gérer les répertoires mais que flock (2) ne le peut pas, alors flock (1) n'est pas implémenté uniquement sur flock (2).
Craig Hicks
27
Vous avez besoin d'une opération atomique, comme flock, sinon cela échouera éventuellement.
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.
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 après un crash, votre script ne sera plus jamais exécuté.
Exécutez ceci plusieurs fois simultanément (comme "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ") et le script perdra plusieurs fois.
Nippysaurus
8
@Nippysaurus: Cette méthode de verrouillage ne fuit pas. Ce que vous avez vu était le script initial se terminant avant que toutes les copies ne soient lancées, donc un autre a pu (correctement) obtenir le verrou. Pour éviter ce faux positif, ajoutez un sleep 10avant rmdiret essayez à nouveau de cascade - rien ne «fuira».
Sir Athos
D'autres sources affirment que mkdir n'est pas atomique sur certains systèmes de fichiers comme NFS. Et d'ailleurs, j'ai vu des occasions où sur NFS mkdir récursif simultané conduisait parfois à des erreurs avec les jobs de matrice jenkins. Je suis donc à peu près sûr que c'est le cas. Mais mkdir est assez sympa pour les cas d'utilisation moins exigeants IMO.
akostadinov
Vous pouvez utiliser l'option noclobber de Bash avec des fichiers normaux.
Palec
26
Pour rendre le verrouillage fiable, vous avez besoin d'une opération atomique. La plupart des propositions ci-dessus ne sont pas atomiques. L'utilitaire proposé lockfile (1) semble prometteur, comme la page de manuel l'a mentionné, qu'il est "résistant à NFS". Si votre OS ne prend pas en charge lockfile (1) et que votre solution doit fonctionner sur NFS, vous n'avez pas beaucoup d'options ...
NFSv2 a deux opérations atomiques:
lien symbolique
Renommer
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 référer 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 rotatifs pour les fichiers et les répertoires (en shell, pas en PHP):
verrouiller le répertoire actuel:
while ! ln -s . lock; do :; done
verrouiller un fichier:
while ! ln -s ${f}${f}.lock; do :; done
déverrouiller le répertoire actuel (hypothèse, le processus en cours a vraiment acquis le verrou):
mv lock deleteme && rm deleteme
déverrouiller un fichier (hypothèse, le processus en cours a vraiment acquis le verrou):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Remove n'est pas non plus atomique, donc d'abord le renommage (qui est atomique) puis le remove.
Pour les appels de lien symbolique et de changement de nom, 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 le fichier et verrouillez-le dans le même répertoire.
Quelles pages de NFS Illustrated prennent en charge l'affirmation selon laquelle mkdir n'est pas atomique sur NFS?
maxschlepzig
Merci pour cette technique. Une implémentation de mutex shell est disponible dans ma nouvelle bibliothèque shell: github.com/Offirmo/offirmo-shell-lib , voir "mutex". Il utilise lockfilesi disponible, ou utilise cette symlinkméthode si ce n'est pas le cas.
Offirmo
Agréable. Malheureusement, cette méthode ne permet pas de supprimer automatiquement les verrous périmés.
Richard Hansen
Pour le déverrouillage en deux étapes ( mv, rm), devrait- rm -fon utiliser, plutôt que rmdans le cas où deux processus P1, P2 sont en course? Par exemple, P1 commence le déverrouillage avec mv, puis P2 se verrouille, puis P2 se déverrouille (à la fois mvet rm), enfin P1 tente rmet échoue.
Matt Wallis
1
@MattWallis Ce dernier problème pourrait facilement être atténué en l'incluant $$dans le ${f}.deletemenom de fichier.
Stefan Majewsky
23
Une autre option consiste à utiliser l' noclobberoption du shell en exécutant set -C. Puis >échouera si le fichier existe déjà.
En bref:
set -C
lockfile="/tmp/locktest.lock"ifecho"$$" > "$lockfile"; thenecho"Successfully acquired lock"# do work
rm "$lockfile"# XXX or via trap - see belowelseecho"Cannot acquire lock - already locked by $(cat "$lockfile")"fi
Cela provoque l'appel du shell:
open(pathname, O_CREAT|O_EXCL)
qui crée atomiquement le fichier ou échoue si le fichier existe déjà.
Selon un commentaire sur BashFAQ 045 , cela peut échouer ksh88, mais cela fonctionne dans tous mes shells:
Intéressant qui pdkshajoute le O_TRUNCdrapeau, mais évidemment c'est redondant:
soit vous créez un fichier vide, soit vous ne faites rien.
La façon dont vous procédez rmdépend de la manière dont vous voulez que les sorties sales soient gérées.
Supprimer à la sortie propre
Les nouvelles exécutions échouent jusqu'à ce que le problème qui a entraîné la sortie impure soit résolu et que le fichier de verrouillage soit supprimé manuellement.
# 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.
Approche très nouvelle ... cela semble être une façon d'atteindre l'atomicité en utilisant un fichier de verrouillage plutôt qu'un répertoire de verrouillage.
Matt Caldwell
Belle approche. :-) Sur le piège EXIT, il devrait restreindre le processus qui peut nettoyer le fichier de verrouillage. Par exemple: trap 'if [[$ (cat "$ lockfile") == "$$"]]; puis rm "$ lockfile"; fi 'EXIT
Kevin Seifert
1
Les fichiers de verrouillage ne sont pas atomiques sur NFS. c'est pourquoi les gens sont passés à l'utilisation de répertoires verrouillés.
K Richard Pixley
20
Vous pouvez l'utiliser GNU Parallelcar il fonctionne comme un mutex lorsqu'il est appelé en tant que sem. Donc, concrètement, vous pouvez utiliser:
sem --id SCRIPTSINGLETON yourScript
Si vous souhaitez également un délai d'attente, utilisez:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Timeout de <0 signifie quitter sans exécuter de script si le sémaphore n'est pas libéré dans le délai, timeout de> 0 signifie exécuter le script quand même.
Notez que vous devez lui donner un nom (avec --id) sinon il est par défaut le terminal de contrôle.
GNU Parallel est une installation très simple sur la plupart des plates-formes Linux / OSX / Unix - ce n'est qu'un script Perl.
Dommage que les gens hésitent à voter contre des réponses inutiles: cela conduit à enfouir de nouvelles réponses pertinentes dans un tas d'ordures.
Dmitry Grigoryev
4
Nous avons juste besoin de beaucoup de votes positifs. C'est une réponse si ordonnée et peu connue. (Bien que pour être pédant, OP voulait rapide et sale alors que c'est rapide et propre!) Plus d'informations sur la semquestion connexe unix.stackexchange.com/a/322200/199525 .
Partiellement nuageux
16
Pour les scripts shell, j'ai tendance à aller avec le mkdirdessus flockcar cela rend les verrous plus portables.
Quoi qu'il en soit, l'utilisation set -ene suffit pas. Cela ne quitte le script que si une commande échoue. Vos serrures seront toujours laissées pour compte.
Pour un nettoyage correct des verrous, vous devez vraiment définir vos pièges sur quelque chose comme ce code psuedo (levé, simplifié et non testé mais à partir de scripts activement utilisés):
#=======================================================================# 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 runningif $(mkdir $LOCK_DIR)
then
mkdir $__lockdir# create this instance's specific lock in queue
LOCK_EXISTS=true# Globalelseecho"FATAL: Lock already exists. Another copy is running or manually lock clean up required."exit 1001 # Or work out some sleep_while_execution_lock elsewherefi
}
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 TRAPstrap __sig_exit EXIT # SIGEXITtrap __sig_int INT # SIGINTtrap __sig_quit QUIT # SIGQUITtrap __sig_term TERM # SIGTERM
mklock
# CODEexit# 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 de sorte que la fonction __sig_exitse produira toujours (sauf un SIGKILL) qui nettoie vos serrures.
Remarque: mes valeurs de sortie ne sont pas des valeurs faibles. Pourquoi? Divers systèmes de traitement par lots créent ou ont des attentes concernant les nombres de 0 à 31. En les définissant sur autre chose, je peux faire réagir mes scripts et mes flux de lots en conséquence au travail ou au script précédent.
Votre script est beaucoup trop détaillé, aurait pu être beaucoup plus court je pense, mais dans l'ensemble, oui, vous devez mettre en place des pièges pour le faire correctement. J'ajouterais aussi SIGHUP.
mojuba
Cela fonctionne bien, sauf qu'il semble vérifier $ LOCK_DIR alors qu'il supprime $ __ lockdir. Peut-être devrais-je suggérer que lors de la suppression du verrou, vous feriez rm -r $ LOCK_DIR?
bevada
Merci pour la suggestion. Le code ci-dessus a été levé et placé à la manière d'un code psuedo, il devra donc être réglé en fonction de l'utilisation des gens. Cependant, j'ai délibérément opté pour rmdir dans mon cas car rmdir supprime en toute sécurité les répertoires uniquement_s'ils sont vides. Si les gens y placent des ressources telles que des fichiers PID, etc., ils devraient modifier leur nettoyage de verrouillage au plus agressif rm -r $LOCK_DIRou même le forcer si nécessaire (comme je l'ai fait aussi dans des cas particuliers tels que la tenue de fichiers de travail relatifs). À votre santé.
Mark Stinson
Avez-vous testé exit 1002?
Gilles Quenot
13
Vraiment rapide et vraiment sale? Ce one-liner en haut de votre script fonctionnera:
Comment simuler cela pour le tester? Existe-t-il un moyen de démarrer un script deux fois en une seule ligne et peut-être obtenir un avertissement, s'il est déjà en cours d'exécution?
rubo77
2
Cela ne fonctionne pas du tout! Pourquoi vérifier -gt 2? grep ne se trouve pas toujours dans le résultat de ps!
rubo77 du
pgrepn'est pas dans POSIX. Si vous voulez que cela fonctionne de manière portable, vous avez besoin de POSIX pset de traiter sa sortie.
Palec
Sur OSX -cn'existe pas, vous devrez utiliser | wc -l. À propos de la comparaison des nombres: -gt 1est vérifiée puisque la première instance se voit.
Benjamin Peter
6
Voici une approche qui combine le verrouillage de répertoire atomique avec une vérification du verrouillage périmé via PID et redémarrer si périmé. De plus, cela ne repose sur aucun bashisme.
#!/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
thenecho"Removing stale lock of nonexistent PID ${PID}" >&2
rm -rf $LOCKDIRecho"Restarting myself (${SCRIPTNAME})" >&2
exec"$0""$@"fiecho"$SCRIPTNAME is already running, bailing out" >&2
exit 1
else# lock successfully acquired, save PIDecho $$ > $PIDFILEfitrap"rm -rf ${LOCKDIR}" QUIT INT TERM EXIT
echo hello
sleep 30s
echobye
Créer un fichier de verrouillage dans un emplacement connu et vérifier son existence au démarrage du script? Mettre le PID dans le fichier peut être utile si quelqu'un tente de retrouver une instance errante qui empêche l'exécution du script.
Cet exemple est expliqué dans le man flock, mais il a besoin de quelques améliorations, car nous devons gérer les bogues 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" ]; thenecho Cannot lock!; exit 1; fiecho $$>>/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 commandsexit$FLOCKEXIT#return properly exitcode, may be usefull inside external scripts
Vous pouvez utiliser une autre méthode, lister les processus 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 pour supprimer le parasite et enfin le compter par grep -c. et comparer avec le nombre. C'est compliqué et incertain
Vous pouvez utiliser ln -s, car cela ne peut créer de lien symbolique que lorsqu'aucun fichier ou lien symbolique n'existe, comme mkdir. de nombreux processus système utilisaient des liens symboliques dans le passé, par exemple init ou inetd. synlink conserve l'identifiant du processus, mais ne pointe vraiment vers rien. pendant des années, ce comportement a changé. processus utilise des flocs et des sémaphores.
Znik
5
Les réponses existantes publiées reposent sur l'utilitaire CLI flockou ne sécurisent pas correctement le fichier de verrouillage. L'utilitaire flock n'est pas disponible sur tous les systèmes non Linux (c'est-à-dire FreeBSD) et ne fonctionne pas correctement sur NFS.
Dans mes premiers jours de l' administration du système et le développement du système, on m'a dit qu'une méthode sûre et relativement portable de créer un fichier de verrouillage était de créer un fichier temporaire en utilisant mkemp(3)oumkemp(1) , à écrire des informations d'identification dans le fichier temporaire (c'est-à-dire PID), puis à un lien physique le fichier temporaire dans le fichier de verrouillage. Si le lien a réussi, vous avez réussi à obtenir le verrou.
Lors de l'utilisation de verrous dans des scripts shell, je place généralement une obtain_lock()fonction dans un profil partagé, puis je 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)
iftest"x${TMPLOCK}" == "x";thenecho"unable to create temporary file with mktemp" 1>&2
return 1
fiecho"$$" > "${TMPLOCK}"# attempt to obtain lock file
ln "${TMPLOCK}""${LOCK}" 2> /dev/null
iftest $? -ne 0;then
rm -f "${TMPLOCK}"echo"unable to obtain lockfile" 1>&2
iftest -f "${LOCK}";thenecho"current lock information held by: $(cat "${LOCK}")" 1>&2
fireturn 2
fi
rm -f "${TMPLOCK}"return 0;
};
Voici un exemple d'utilisation de la fonction de verrouillage:
Lorsque je cible une machine Debian, je trouve que le lockfile-progspaquet est une bonne solution. procmailest également livré avec un lockfileoutil. Cependant, parfois je suis coincé avec aucun de ces derniers.
Voici ma solution qui utilise mkdirpour 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_requirelorsque vous avez besoin d'un accès exclusif à quelque chose. Un paramètre de nom de verrou facultatif vous permet de partager des verrous entre différents scripts. Il existe également deux fonctions de niveau inférieur ( exclusive_lock_tryet 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" ]
thenlocal 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 processecho"\"$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="$$"fifiif [ "`trap -p EXIT`" != "" ]
then# already have an EXIT trapecho"Cannot get lock, already have an EXIT trap"return 1
fiif [ "$LOCK_PID" != "$$" ] &&
! ( umask 077 && mkdir "$LOCK_DIR" && umask 177 && echo $$ > "$LOCK_PID_FILE" ) 2> /dev/null
thenlocal LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"# unable to acquire lock, new process got in firstecho"\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return 1
fitrap"/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" ]
doif [ "$TRIES" -gt 0 ]
then
sleep "$DELAY"filocal 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 ]
thenreturn 0
fidonereturn"$LOCK_RETVAL"
}
function exclusive_lock_require() # [lockname] [retries] [delay]
{
if ! exclusive_lock_retry "$@"thenexit 1
fi
}
Merci, je l'ai essayé sur cygwin moi-même et il a passé des tests simples.
ndemou le
4
Si les limitations de flock, qui ont déjà été décrites ailleurs sur ce fil, ne sont pas un problème pour vous, alors 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
Je pensais juste que je soulignerais que -x (verrouillage en écriture) est déjà défini par défaut.
Keldon Alleyne
et le -nfera exit 1immédiatement s'il ne peut pas obtenir la serrure
Anentropic
Merci @KeldonAlleyne, j'ai mis à jour le code pour supprimer "-x" car il est par défaut.
presto8
3
Certains unix ont lockfilece qui est très similaire à celui déjà mentionné flock.
Depuis la page de manuel:
lockfile peut être utilisé pour créer un ou plusieurs fichiers de sémaphore. Si lock-file ne peut pas créer tous les fichiers spécifiés (dans l'ordre spécifié), il attend le temps d'arrêt (par défaut à 8) secondes et réessaye le dernier fichier qui n'a pas réussi. Vous pouvez spécifier le nombre de tentatives à effectuer jusqu'à ce que l'échec soit renvoyé. Si le nombre de tentatives est -1 (par défaut, c'est-à-dire -r-1), lockfile réessaiera indéfiniment.
lockfileest distribué avec procmail. Il existe également une alternative dotlockfilequi va avec le liblockfilepackage. Ils prétendent tous deux travailler de manière fiable sur NFS.
Mr. Deathless
3
En fait, bien que la réponse de bmdhacks soit presque bonne, il y a une légère chance que le second script s'exécute après avoir vérifié le fichier de verrouillage et avant de l'écrire. Ainsi, ils écriront tous les deux le fichier de verrouillage et ils fonctionneront 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 ; thentrap'rm -f "$lockfile"; exit $?' INT TERM EXIT
else# or you can decide to skip the "else" part if you wantecho"Another instance is already running!"fi
le noclobber option s'assurera que la commande de redirection échouera si le fichier existe déjà. Donc, la commande de redirection est en fait 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.
PS 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 pour réduire le risque de laisser le fichier de verrouillage 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 périmés.
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 à exécution longue.
J'utilise noclobber pour m'assurer qu'un seul script peut ouvrir et écrire dans le fichier de verrouillage à la fois. De plus, je stocke suffisamment d'informations pour identifier de manière unique un processus dans le fichier de verrouillage. Je définis l'ensemble de données pour identifier de manière unique un processus comme étant pid, ppid, lstart.
Lorsqu'un nouveau script démarre, s'il ne parvient pas à créer le fichier de verrouillage, il vérifie alors que le processus qui a créé le fichier de verrouillage est toujours là. Sinon, nous supposons que le processus d'origine est mort d'une mort honteuse et a laissé un fichier de verrouillage périmé. Le nouveau script prend alors possession du fichier de verrouillage, et tout est bien le monde, encore une fois.
Devrait fonctionner avec plusieurs shells sur plusieurs plates-formes. Rapide, portable et simple.
#!/usr/bin/env sh# Author: rouble
LOCKFILE=/var/tmp/lockfile #customize this linetrap 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
elseif [ -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" ]; thenecho"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
elseecho"Cannot write to $LOCKFILE. Error." >&2
return 1
fifielseecho"Do you have write permissons to $LOCKFILE ?" >&2
return 1
fifi
}
# 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 $LOCKFILEfi
}
# Test code# int main( int argc, const char* argv[] )echo"Acquring lock."
acquire
if [ $? -eq 0 ]; thenecho"Acquired lock."read -p "Press [Enter] key to release lock..."
release
echo"Released lock."elseecho"Unable to acquire lock."fi
Je vous ai donné +1 pour une solution différente. Bien que cela ne fonctionne pas non plus sous AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: liste invalide avec -o.) Pas HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: option illégale - o). Merci.
Tagar
3
Je voulais supprimer les fichiers de verrouillage, les lockdirs, les programmes de verrouillage spéciaux et même pidofparce qu'ils ne se trouvent pas sur toutes les installations Linux. Je voulais également avoir le code le plus simple possible (ou au moins le moins de lignes possible). Déclaration la plus simple if, en une seule ligne:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; thenecho"Already running"; exit; fi
Ceci est sensible à la sortie 'ps', sur ma machine (Ubuntu 14.04, / bin / ps de procps-ng version 3.3.9) la commande 'ps axf' imprime des caractères d'arbre ascii qui perturbent les numéros de champ. Cela a fonctionné pour moi: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Si vous voulez plus de journalisation, utilisez celui-ci
[ "${FLOCKER}" != "$0" ] && { echo"Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo"Lock is free. Completing."
Cela définit et vérifie les verrous en utilisant flock utilitaire. Ce code détecte s'il a été exécuté pour la première fois en vérifiant la variable FLOCKER, s'il n'est pas défini sur le nom du script, puis il essaie de redémarrer le script de manière récursive en utilisant flock et avec la variable FLOCKER initialisée, si FLOCKER est correctement défini, puis flock à l'itération précédente réussi et vous pouvez continuer. Si le verrou est occupé, il échoue avec un code de sortie configurable.
Il semble ne pas fonctionner sur Debian 7, mais semble fonctionner à nouveau avec le paquet expérimental util-linux 2.25. Il écrit "flock: ... Fichier texte occupé". Il peut être remplacé en désactivant l'autorisation d'écriture sur votre script.
Les fichiers PID et lockfiles sont certainement les plus fiables. Lorsque vous essayez d'exécuter le programme, il peut vérifier le fichier de verrouillage qui et s'il existe, il peut utiliser pspour voir si le processus est toujours en cours d'exécution. Si ce n'est pas le cas, le script peut démarrer et mettre à jour le PID du fichier de verrouillage vers le sien.
Je trouve que la solution de bmdhack est la plus pratique, du moins pour mon cas d'utilisation. L'utilisation de flock et lockfile repose sur la suppression du lockfile en utilisant rm lorsque le script se termine, ce qui ne peut pas toujours être garanti (par exemple, kill -9).
Je changerais une chose mineure à propos de la solution de bmdhack: cela met un point d'honneur à supprimer le fichier de verrouillage, sans déclarer que cela n'est pas nécessaire 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 mort sera simplement ignoré / écrasé.
Ma solution simplifiée est donc d'ajouter simplement ce qui suit en haut de votre singleton:
## Test the lock
LOCKFILE=/tmp/singleton.lock
if [ -e ${LOCKFILE} ] && kill -0 `cat ${LOCKFILE}`; thenecho"Script already running. bye!"exitfi## 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 course, car le test de verrouillage et les opérations de positionnement ne sont pas une seule action atomique. Mais la solution proposée pour cela par lhunath d'utiliser mkdir a le défaut 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 discuté ci-dessus, par exemple par presto8) pour implémenter un sémaphore de comptage . Il active n'importe quel nombre spécifique de processus simultanés que vous souhaitez. Nous l'utilisons pour limiter le niveau de concurrence de divers processus de travail de file d'attente.
C'est comme sem mais beaucoup plus léger. (Divulgation complète: je l'ai écrit après avoir trouvé que le sem était beaucoup trop lourd pour nos besoins et qu'il n'y avait pas d'utilitaire de comptage simple de sémaphore disponible.)
Un exemple avec flock (1) mais sans sous-coque. flock () ed file / tmp / foo n'est jamais supprimé, mais cela n'a pas d'importance car il obtient flock () et un-flock () ed.
#!/bin/bashexec 9<> /tmp/foo
flock -n 9
RET=$?
if [[ $RET -ne 0 ]] ; thenecho"lock failed, exiting"exitfi#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
Déjà répondu un million de fois, mais d'une autre manière, sans avoir besoin 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
fiecho $$ > $LOCK_FILE
Chaque fois qu'il écrit le PID actuel ($$) dans le fichier de verrouillage et au démarrage du script, il vérifie si un processus est en cours d'exécution avec le dernier PID.
Sans l'appel d'interruption (ou au moins un nettoyage vers la fin pour le cas normal), vous avez le bogue faux positif où le fichier de verrouillage est laissé après la dernière exécution et le PID a été réutilisé par un autre processus plus tard. (Et dans le pire des cas, il a été doué pour un processus de longue durée comme Apache ....)
Philippe Chaintreuil
1
Je suis d'accord, mon approche est imparfaite, elle a besoin d'un piège. J'ai mis à jour ma solution. Je préfère toujours ne pas avoir de dépendances externes.
Filidor Wiese
1
L'utilisation du verrou du processus est beaucoup plus forte et prend également en charge les sorties peu gracieuses. lock_file reste ouvert tant 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; thenecho"WARNING: Other instance of $(basename $0) running."exit 1
fiexec 3> $lock_file
Le chemin du troupeau est la voie à suivre. Pensez à ce qui se passe lorsque le script meurt soudainement. Dans le cas du troupeau, vous perdez simplement le troupeau, mais ce n'est pas un problème. Notez également qu'une mauvaise astuce est de prendre un troupeau sur le script lui-même ... mais cela vous permet bien sûr de vous lancer à plein régime dans les problèmes de permission.
Cela peut ou non être un problème, selon la façon dont il est utilisé, mais il y a une condition de concurrence entre le test du verrou et sa création, de sorte que deux scripts puissent tous les deux être démarrés en même temps. Si l'un se termine en premier, l'autre continuera de fonctionner sans fichier de verrouillage.
TimB
3
C News, qui m'a beaucoup appris sur les scripts shell portables, avait l'habitude de créer un fichier lock. $$, puis d'essayer de le lier avec "lock" - si le lien réussit, vous aviez le verrou, sinon vous avez supprimé le verrou. $$ et est sorti.
Paul Tomblin
C'est un très bon moyen de le faire, sauf que vous souffrez toujours de la nécessité de supprimer le fichier de verrouillage manuellement si quelque chose ne va pas et que le fichier de verrouillage n'est pas supprimé.
Réponses:
Voici une implémentation qui utilise un fichier de verrouillage et y fait écho 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 celle
kill -0
qui ne délivre aucun signal mais vérifie simplement si un processus avec le PID donné existe. L'appel àtrap
garantira également que le fichier de verrouillage est supprimé même lorsque votre processus est tué (saufkill -9
).la source
Utilisez
flock(1)
pour faire d'un verrou à portée exclusive un 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
(
et)
n'est exécuté que par un processus à la fois et que le processus n'attend pas trop longtemps un verrou.Attention: cette commande particulière fait partie de
util-linux
. Si vous exécutez un système d'exploitation autre que Linux, il peut être disponible ou non.la source
( command A ) command B
invoque un sous-shell pourcommand A
. Documenté sur tldp.org/LDP/abs/html/subshells.html . Je ne suis toujours pas sûr du moment de l'invocation du sous-shell et de la commande B.if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
sorte que si le délai d'attente se produit (un autre processus a le fichier verrouillé), ce script ne continue pas et ne modifie pas le fichier. Probablement ... le contre-argument est `` mais si cela a pris 10 secondes et que le verrou n'est toujours pas disponible, il ne sera jamais disponible '', probablement parce que le processus qui maintient le verrou ne se termine pas (peut-être est-il en cours d'exécution sous un débogueur?).exit
est de la partie à l'intérieur du(
)
. Lorsque le sous-processus se termine, le verrou est automatiquement libéré, car aucun processus ne le retient.Toutes les approches qui testent l'existence de "fichiers verrouillés" 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 qui rendra vos tentatives d'exclusion mutuelle interrompues.
Au lieu de cela, vous devez utiliser
mkdir
.mkdir
crée un répertoire s'il n'existe pas encore, et si c'est le cas, il 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, consultez l'excellent BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Si vous souhaitez éliminer les verrous périmés, l' unité de fusion (1) est très pratique. Le seul inconvénient ici est que l'opération prend environ une seconde, donc ce n'est pas instantané.
Voici une fonction que j'ai écrite une fois qui résout le problème d'utilisation de l'unité de fusion:
# 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 boîtier UNIX), le fuser de Linux (1) offre des options supplémentaires et il y a aussi flock (1) .
la source
if ! mkdir
partie en vérifiant si le processus avec le PID stocké (lors du démarrage réussi) à l'intérieur du lockdir est réellement en cours d'exécution et identique au script pour la protection stalenes. Cela protégerait également contre la réutilisation du PID après un redémarrage, et ne nécessiterait même pasfuser
.mkdir
n'est pas défini comme une opération atomique et en tant que tel, «effet secondaire» est un détail d'implémentation du système de fichiers. Je le crois pleinement s'il dit que NFS ne l'implémente pas de manière atomique. Bien que je ne soupçonne pas que votre/tmp
sera un partage NFS et sera probablement fourni par un fs qui implémentemkdir
atomiquement.ln
pour créer un lien physique à partir d'un autre fichier. Si vous avez des systèmes de fichiers étranges qui ne garantissent pas cela, vous pouvez vérifier l'inode du nouveau fichier par la suite pour voir s'il est le même que le fichier d'origine.open(... O_CREAT|O_EXCL)
. Pour ce faire, vous avez juste besoin d'un programme utilisateur approprié, tel quelockfile-create
(inlockfile-progs
) oudotlockfile
(inliblockfile-bin
). Et assurez-vous de nettoyer correctement (par exempletrap EXIT
), ou testez les verrous périmés (par exemple avec--use-pid
).Il y a un wrapper autour de l'appel système flock (2) appelé, sans imagination, flock (1). Cela rend relativement facile l'obtention de verrous exclusifs de manière fiable sans se soucier du nettoyage, etc. Il y a des exemples sur la page de manuel pour savoir comment l'utiliser dans un script shell.
la source
flock()
appel système n'est pas POSIX et ne fonctionne pas pour les fichiers sur les montages NFS.flock -x -n %lock file% -c "%command%"
pour m'assurer qu'une seule instance est en cours d'exécution.Vous avez besoin d'une opération atomique, comme flock, sinon cela échouera éventuellement.
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.
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 après un crash, votre script ne sera plus jamais exécuté.
la source
sleep 10
avantrmdir
et essayez à nouveau de cascade - rien ne «fuira».Pour rendre le verrouillage fiable, vous avez besoin d'une opération atomique. La plupart des propositions ci-dessus ne sont pas atomiques. L'utilitaire proposé lockfile (1) semble prometteur, comme la page de manuel l'a mentionné, qu'il est "résistant à NFS". Si votre OS ne prend pas en charge lockfile (1) et que 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 référer 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 rotatifs pour les fichiers et les répertoires (en shell, pas en PHP):
verrouiller le répertoire actuel:
while ! ln -s . lock; do :; done
verrouiller un fichier:
while ! ln -s ${f} ${f}.lock; do :; done
déverrouiller le répertoire actuel (hypothèse, le processus en cours a vraiment acquis le verrou):
déverrouiller un fichier (hypothèse, le processus en cours a vraiment acquis le verrou):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Remove n'est pas non plus atomique, donc d'abord le renommage (qui est atomique) puis le remove.
Pour les appels de lien symbolique et de changement de nom, 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 le fichier et verrouillez-le dans le même répertoire.
la source
lockfile
si disponible, ou utilise cettesymlink
méthode si ce n'est pas le cas.mv
,rm
), devrait-rm -f
on utiliser, plutôt querm
dans le cas où deux processus P1, P2 sont en course? Par exemple, P1 commence le déverrouillage avecmv
, puis P2 se verrouille, puis P2 se déverrouille (à la foismv
etrm
), enfin P1 tenterm
et échoue.$$
dans le${f}.deleteme
nom de fichier.Une autre option consiste à utiliser l'
noclobber
option du shell en exécutantset -C
. Puis>
é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:
qui crée atomiquement le fichier ou échoue si le fichier existe déjà.
Selon un commentaire sur BashFAQ 045 , cela peut échouer
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
Intéressant qui
pdksh
ajoute leO_TRUNC
drapeau, mais évidemment c'est redondant:soit vous créez un fichier vide, soit vous ne faites rien.
La façon dont vous procédez
rm
dépend de la manière dont vous voulez que les sorties sales soient gérées.Supprimer à la sortie propre
Les nouvelles exécutions échouent jusqu'à ce que le problème qui a entraîné la sortie impure soit résolu et que le fichier de verrouillage soit supprimé manuellement.
# 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
la source
Vous pouvez l'utiliser
GNU Parallel
car il fonctionne comme un mutex lorsqu'il est appelé en tant quesem
. Donc, concrètement, vous pouvez utiliser:Si vous souhaitez également un délai d'attente, utilisez:
Timeout de <0 signifie quitter sans exécuter de script si le sémaphore n'est pas libéré dans le délai, timeout de> 0 signifie exécuter le script quand même.
Notez que vous devez lui donner un nom (avec
--id
) sinon il est par défaut le terminal de contrôle.GNU Parallel
est une installation très simple sur la plupart des plates-formes Linux / OSX / Unix - ce n'est qu'un script Perl.la source
sem
question connexe unix.stackexchange.com/a/322200/199525 .Pour les scripts shell, j'ai tendance à aller avec le
mkdir
dessusflock
car cela rend les verrous plus portables.Quoi qu'il en soit, l'utilisation
set -e
ne suffit pas. Cela ne quitte le script que si une commande échoue. Vos serrures seront toujours laissées pour compte.Pour un nettoyage correct des verrous, vous devez vraiment définir vos pièges sur quelque chose comme ce code psuedo (levé, simplifié et non testé mais à partir de scripts activement utilisés):
#======================================================================= # 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 de sorte que la fonction
__sig_exit
se produira toujours (sauf un SIGKILL) qui nettoie vos serrures.Remarque: mes valeurs de sortie ne sont pas des valeurs faibles. Pourquoi? Divers systèmes de traitement par lots créent ou ont des attentes concernant les nombres de 0 à 31. En les définissant sur autre chose, je peux faire réagir mes scripts et mes flux de lots en conséquence au travail ou au script précédent.
la source
rm -r $LOCK_DIR
ou même le forcer si nécessaire (comme je l'ai fait aussi dans des cas particuliers tels que la tenue de fichiers de travail relatifs). À votre santé.exit 1002
?Vraiment rapide et vraiment sale? Ce one-liner en haut de votre script fonctionnera:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Bien sûr, assurez-vous simplement que le nom de votre script est unique. :)
la source
-gt 2
? grep ne se trouve pas toujours dans le résultat de ps!pgrep
n'est pas dans POSIX. Si vous voulez que cela fonctionne de manière portable, vous avez besoin de POSIXps
et de traiter sa sortie.-c
n'existe pas, vous devrez utiliser| wc -l
. À propos de la comparaison des nombres:-gt 1
est vérifiée puisque la première instance se voit.Voici une approche qui combine le verrouillage de répertoire atomique avec une vérification du verrouillage périmé via PID et redémarrer si périmé. De plus, cela ne repose sur aucun bashisme.
#!/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
la source
Créer un fichier de verrouillage dans un emplacement connu et vérifier son existence au démarrage du script? Mettre le PID dans le fichier peut être utile si quelqu'un tente de retrouver une instance errante qui empêche l'exécution du script.
la source
Cet exemple est expliqué dans le man flock, mais il a besoin de quelques améliorations, car nous devons gérer les bogues 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, lister les processus 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 pour supprimer le parasite et enfin le compter par grep -c. et comparer avec le nombre. C'est compliqué et incertain
la source
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 (c'est-à-dire FreeBSD) et ne fonctionne pas correctement sur NFS.Dans mes premiers jours de l' administration du système et le développement du système, on m'a dit qu'une méthode sûre et relativement portable de créer un fichier de verrouillage était de créer un fichier temporaire en utilisant
mkemp(3)
oumkemp(1)
, à écrire des informations d'identification dans le fichier temporaire (c'est-à-dire PID), puis à un lien physique le fichier temporaire dans le fichier de verrouillage. Si le lien a réussi, vous avez réussi à obtenir le verrou.Lors de l'utilisation de verrous dans des scripts shell, je place généralement une
obtain_lock()
fonction dans un profil partagé, puis je 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
à tous les points de sortie de votre script.J'ai utilisé ce qui précède dans les environnements Linux et FreeBSD.
la source
Lorsque je cible une machine Debian, je trouve que le
lockfile-progs
paquet est une bonne solution.procmail
est également livré avec unlockfile
outil. Cependant, parfois je suis coincé avec aucun de ces derniers.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 de nom de verrou facultatif vous permet de partager des verrous entre différents scripts. Il existe également deux fonctions de niveau inférieur (exclusive_lock_try
etexclusive_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 }
la source
Si les limitations de flock, qui ont déjà été décrites ailleurs sur ce fil, ne sont pas un problème pour vous, alors 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
la source
-n
feraexit 1
immédiatement s'il ne peut pas obtenir la serrureCertains unix ont
lockfile
ce qui est très similaire à celui déjà mentionnéflock
.Depuis la page de manuel:
la source
lockfile
utilitaire ??lockfile
est distribué avecprocmail
. Il existe également une alternativedotlockfile
qui va avec leliblockfile
package. Ils prétendent tous deux travailler de manière fiable sur NFS.En fait, bien que la réponse de bmdhacks soit presque bonne, il y a une légère chance que le second script s'exécute après avoir vérifié le fichier de verrouillage et avant de l'écrire. Ainsi, ils écriront tous les deux le fichier de verrouillage et ils fonctionneront 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
le
noclobber
option s'assurera que la commande de redirection échouera si le fichier existe déjà. Donc, la commande de redirection est en fait 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.PS 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 pour réduire le risque de laisser le fichier de verrouillage après l'arrêt du script avec Ctrl-C par exemple. C'est donc la solution complète
la source
J'utilise une approche simple qui gère les fichiers de verrouillage périmés.
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 à exécution longue.
J'utilise noclobber pour m'assurer qu'un seul script peut ouvrir et écrire dans le fichier de verrouillage à la fois. De plus, je stocke suffisamment d'informations pour identifier de manière unique un processus dans le fichier de verrouillage. Je définis l'ensemble de données pour identifier de manière unique un processus comme étant pid, ppid, lstart.
Lorsqu'un nouveau script démarre, s'il ne parvient pas à créer le fichier de verrouillage, il vérifie alors que le processus qui a créé le fichier de verrouillage est toujours là. Sinon, nous supposons que le processus d'origine est mort d'une mort honteuse et a laissé un fichier de verrouillage périmé. Le nouveau script prend alors possession du fichier de verrouillage, et tout est bien le monde, encore une fois.
Devrait fonctionner avec plusieurs shells 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
la source
Je voulais supprimer les fichiers de verrouillage, les lockdirs, les programmes de verrouillage spéciaux et même
pidof
parce qu'ils ne se trouvent pas sur toutes les installations Linux. Je voulais également avoir le code le plus simple possible (ou au moins le moins de lignes possible). Déclaration la plus simpleif
, en une seule ligne:if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
la source
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Ajoutez cette ligne au début de votre script
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
C'est un code standard de Man Flock.
Si vous voulez plus de journalisation, utilisez celui-ci
[ "${FLOCKER}" != "$0" ] && { echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi"; } || echo "Lock is free. Completing."
Cela définit et vérifie les verrous en utilisant
flock
utilitaire. Ce code détecte s'il a été exécuté pour la première fois en vérifiant la variable FLOCKER, s'il n'est pas défini sur le nom du script, puis il essaie de redémarrer le script de manière récursive en utilisant flock et avec la variable FLOCKER initialisée, si FLOCKER est correctement défini, puis flock à l'itération précédente réussi et vous pouvez continuer. Si le verrou est occupé, il échoue avec un code de sortie configurable.Il semble ne pas fonctionner sur Debian 7, mais semble fonctionner à nouveau avec le paquet expérimental util-linux 2.25. Il écrit "flock: ... Fichier texte occupé". Il peut être remplacé en désactivant l'autorisation d'écriture sur votre script.
la source
Les fichiers PID et lockfiles sont certainement les plus fiables. Lorsque vous essayez d'exécuter le programme, il peut vérifier le fichier de verrouillage qui et s'il existe, il 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 et mettre à jour le PID du fichier de verrouillage vers le sien.la source
Je trouve que la solution de bmdhack est la plus pratique, du moins pour mon cas d'utilisation. L'utilisation de flock et lockfile repose sur la suppression du lockfile en utilisant rm lorsque le script se termine, ce qui ne peut pas toujours être garanti (par exemple, kill -9).
Je changerais une chose mineure à propos de la solution de bmdhack: cela met un point d'honneur à supprimer le fichier de verrouillage, sans déclarer que cela n'est pas nécessaire 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 mort sera simplement ignoré / écrasé.
Ma solution simplifiée est donc d'ajouter simplement ce qui suit 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 course, car le test de verrouillage et les opérations de positionnement ne sont pas une seule action atomique. Mais la solution proposée pour cela par lhunath d'utiliser mkdir a le défaut qu'un script tué peut laisser derrière le répertoire, empêchant ainsi d'autres instances de s'exécuter.
la source
L' utilitaire sémaphorique utilise
flock
(comme discuté ci-dessus, par exemple par presto8) pour implémenter un sémaphore de comptage . Il active n'importe quel nombre spécifique de processus simultanés que vous souhaitez. Nous l'utilisons pour limiter le niveau de concurrence de divers processus de travail de file d'attente.C'est comme sem mais beaucoup plus léger. (Divulgation complète: je l'ai écrit après avoir trouvé que le sem était beaucoup trop lourd pour nos besoins et qu'il n'y avait pas d'utilitaire de comptage simple de sémaphore disponible.)
la source
Un exemple avec flock (1) mais sans sous-coque. flock () ed file / 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
la source
Déjà répondu un million de fois, mais d'une autre manière, sans avoir besoin 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 et au démarrage du script, il vérifie si un processus est en cours d'exécution avec le dernier PID.
la source
L'utilisation du verrou du processus est beaucoup plus forte et prend également en charge les sorties peu gracieuses. lock_file reste ouvert tant 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
la source
J'utilise oneliner @ au tout début du script:
#!/bin/bash if [[ $(pgrep -afc "$(basename "$0")") -gt "1" ]]; then echo "Another instance of "$0" has already been started!" && exit; fi . the_beginning_of_actual_script
Il est bon de voir la présence du processus dans la mémoire (quel que soit l'état du processus); mais il fait le travail pour moi.
la source
Le chemin du troupeau est la voie à suivre. Pensez à ce qui se passe lorsque le script meurt soudainement. Dans le cas du troupeau, vous perdez simplement le troupeau, mais ce n'est pas un problème. Notez également qu'une mauvaise astuce est de prendre un troupeau sur le script lui-même ... mais cela vous permet bien sûr de vous lancer à plein régime dans les problèmes de permission.
la source
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
la source