Quel est le moyen le plus simple/le meilleur de s’assurer que seulement une instance d’un script est en cours d’exécution - en supposant que ce soit Bash sous Linux?
En ce moment je fais:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
mais il a plusieurs problèmes:
-C
vérifie uniquement les 14 premiers caractères du nom du processusBien sûr, je peux écrire ma propre gestion de pidfile, mais je sens qu’il devrait exister un moyen simple de le faire.
Si le script est identique pour tous les utilisateurs, vous pouvez utiliser une approche lockfile
. Si vous obtenez le verrou, continuez sinon afficher un message et quitter.
Par exemple:
[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $
[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"
[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $
[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $
Après /tmp/the.lock
a été acquis, votre script sera le seul à avoir accès à l'exécution. Lorsque vous avez terminé, enlevez le verrou. Sous forme de script, cela pourrait ressembler à:
#!/bin/bash
lockfile -r 0 /tmp/the.lock || exit 1
# Do stuff here
rm -f /tmp/the.lock
Le verrouillage consultatif a été utilisé pendant des siècles et il peut être utilisé dans des scripts bash. Je préfère les simples flock
(de util-linux[-ng]
) sur lockfile
(de procmail
). Et souvenez-vous toujours d’un piège à la sortie (sigspec == EXIT
ou 0
, piéger des signaux spécifiques est superflu) dans ces scripts.
En 2009, j'ai publié mon script passe-partout verrouillable (disponible à l'origine sur ma page wiki, aujourd'hui sous la forme Gist ). Transformer cela en une instance par utilisateur est simple. En l'utilisant, vous pouvez également écrire facilement des scripts pour d'autres scénarios nécessitant un verrouillage ou une synchronisation.
Voici le passe-partout mentionné pour votre commodité.
#!/bin/bash
# SPDX-License-Identifier: MIT
## Copyright (C) 2009 Przemyslaw Pawelczyk <[email protected]>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate
### HEADER ###
LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99
# PRIVATE
_lock() { flock -$1 $LOCKFD; }
_no_more_locking() { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking() { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }
# ON START
_prepare_locking
# PUBLIC
exlock_now() { _lock xn; } # obtain an exclusive lock immediately or fail
exlock() { _lock x; } # obtain an exclusive lock
shlock() { _lock s; } # obtain a shared lock
unlock() { _lock u; } # drop a lock
### BEGIN OF SCRIPT ###
# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1
# Remember! Lock file is removed when one of the scripts exits and it is
# the only script holding the lock or lock is not acquired at all.
Je pense que flock
est probablement la variante la plus facile (et la plus mémorable). Je l'utilise dans un travail cron pour encoder automatiquement dvds et cds
# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock my_bash_command
Utilisation -w
pour les délais d'expiration ou les options d'exclusion d'attendre le relâchement du verrouillage. Enfin, la page de manuel montre un bel exemple pour plusieurs commandes:
(
flock -n 9 || exit 1
# ... commands executed under lock ...
) 9>/var/lock/mylockfile
set -o noclobber
_ et essayez de remplacer un fichier commun.if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
exit 1 # the global.lock already exists
fi
# ... remainder of script ...
Cet exemple attendra le global.lock
fichier mais expiration après trop longtemps.
function lockfile_waithold()
{
declare -ir time_beg=$(date '+%s')
declare -ir time_max=7140 # 7140 s = 1 hour 59 min.
# poll for lock file up to ${time_max}s
# put debugging info in lock file in case of issues ...
while ! \
(set -o noclobber ; \
echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \
) 2>/dev/null
do
if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
return 1
fi
sleep 1
done
return 0
}
function lockfile_release()
{
rm -f /tmp/global.lock
}
if ! lockfile_waithold ; then
exit 1
fi
trap lockfile_release EXIT
# ... remainder of script ...
(Ceci est similaire à ce post par @Barry Kelly qui a été remarqué par la suite.)
Je ne suis pas sûr qu'il existe une solution robuste en une ligne, de sorte que vous pourriez vous retrouver vous-même.
Les fichiers de verrouillage sont imparfaits, mais moins que d'utiliser 'ps | grep | pipeline de grep -v '.
Cela dit, vous pouvez envisager de séparer le contrôle de processus de votre script: créez un script de démarrage. Ou, du moins, intégrez-le aux fonctions détenues dans un fichier séparé, de sorte que, dans le script de l'appelant, vous pourriez avoir:
. my_script_control.ksh
# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15
dans chaque script qui a besoin de la logique de contrôle. Le trap garantit que le fichier de verrouillage est supprimé lorsque l'appelant quitte. Vous n'avez donc pas à le coder à chaque point de sortie du script.
L'utilisation d'un script de contrôle distinct signifie que vous pouvez vérifier l'intégrité des cas Edge: supprimez les fichiers journaux obsolètes, vérifiez que le fichier de verrouillage est correctement associé à une instance du script en cours d'exécution, donnez la possibilité de tuer le processus en cours d'exécution, etc. Cela signifie également que vous avez une meilleure chance d'utiliser grep sur la sortie ps
. Un ps-grep peut être utilisé pour vérifier qu'un fichier en cours est associé à un processus en cours d'exécution. Vous pourriez peut-être nommer vos fichiers de verrouillage de manière à inclure des informations sur le processus: utilisateur, pid, etc., qui pourront être utilisées par une invocation de script ultérieure pour décider si le processus qui a créé le fichier de verrouillage existe toujours.
[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"
currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
sleep 11111111111111111
else
echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n"
false
exit 1
fi
"lsof -t" pour lister tous les pids des scripts en cours d'exécution nommés "$ 0".
Commande "lsof" fera deux avantages.
j'ai trouvé ceci dans les dépendances du paquet procmail:
apt install liblockfile-bin
Courir: dotlockfile -l file.lock
file.lock sera créé.
Déverouiller: dotlockfile -u file.lock
Utilisez ceci pour lister ce paquet fichiers/commande: dpkg-query -L liblockfile-bin
Les distributions Ubuntu/Debian ont le start-stop-daemon
outil qui a le même objectif que vous le décrivez. Voir aussi / etc/init.d/skeleton pour voir comment il est utilisé pour écrire les scripts de démarrage/d'arrêt.
- Noé
Une solution ultime en ligne:
[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"
Je vous recommande également de regarder chpst (composant de runit):
chpst -L /tmp/your-lockfile.loc ./script.name.sh
J'ai eu le même problème, et est venu avec un modèle qui utilise lockfile, un fichier pid qui contient le numéro d'identification du processus, et un kill -0 $(cat $pid_file)
vérifier que les scripts abandonnés n'arrêtent pas le prochaine course. Cela crée un dossier foobar- $ USERID dans/tmp où vivent les fichiers lockfile et pid.
Vous pouvez toujours appeler le script et faire d'autres choses, tant que vous conservez ces actions dans alertRunningPS
.
#!/bin/bash
user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$
function alertRunningPS () {
local PID=$(cat "$pid_file" 2> /dev/null)
echo "Lockfile present. ps id file: $PID"
echo "Checking if process is actually running or something left over from crash..."
if kill -0 $PID 2> /dev/null; then
echo "Already running, exiting"
exit 1
else
echo "Not running, removing lock and continuing"
rm -f "$lock_file"
lockfile -r 0 "$lock_file"
fi
}
echo "Hello, checking some stuff before locking stuff"
# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS
# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"
sleep 30s
rm -f "$lock_file"
rm -f "$pid_file"
exit 0