linux - programas - ¿Cuál es la mejor manera de garantizar que solo se ejecute una instancia de un script Bash?
scripts bash ejemplos (14)
Use la opción set -o noclobber
e intente sobrescribir un archivo común.
Un pequeño ejemplo
if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
exit 1 # the global.lock already exists
fi
# ...remainder of script...
Un ejemplo más largo. Este ejemplo esperará el bloqueo global, pero el tiempo de espera después de demasiado tiempo.
function lockfile_waithold()
{
declare -ir time_beg=$(date ''+%s'')
declare -ir maxtime=7140 # 7140 s = 1 hour 59 min.
# waiting up to ${maxtime}s for /tmp/global.lock ...
while ! /
(set -o noclobber ; /
echo -e "DATE:$(date)/nUSER:$(whoami)/nPID:$$" > /tmp/global.lock /
) 2>/dev/null
do
if [ $(( $(date ''+%s'') - ${time_beg})) -gt ${maxtime} ] ; then
echo "waited too long for /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
# ...remainder of script
lockfile_release
Repost desde aquí por @Barry Kelly.
Esta pregunta ya tiene una respuesta aquí:
¿Cuál es la mejor / la mejor forma de garantizar que solo se ejecute una instancia de un determinado guión, suponiendo que sea Bash en Linux?
En este momento estoy haciendo:
ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh
pero tiene varios problemas:
- pone el cheque fuera de la secuencia de comandos
- no me permite ejecutar el mismo script desde cuentas separadas, lo que a veces me gustaría.
-
-C
verifica solo los primeros 14 caracteres del nombre del proceso
Por supuesto, puedo escribir mi propio manejo de archivos pidfile, pero siento que debería haber una manera simple de hacerlo.
primer ejemplo de prueba
[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"
segundo ejemplo de prueba
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
explicación
"lsof -t" para listar todos los pides de las secuencias de comandos actuales en ejecución llamado "$ 0".
El comando "lsof" tendrá dos ventajas.
- Ignore pids que está editando un editor como vim, porque vim edita su archivo de mapeo como ".file.swp".
- Ignore los picos bifurcados por las secuencias de comandos actuales de shell en ejecución, que la mayoría de los comandos derivativos "grep" no pueden lograr. Utilice el comando "pstree -pH pidnum" para ver detalles sobre el estado de bifurcación del proceso actual.
Aquí está nuestro bit estándar. Puede recuperarse de la secuencia de comandos de alguna manera morir sin limpiar su archivo de bloqueo.
Escribe la ID del proceso en el archivo de bloqueo si se ejecuta normalmente. Si encuentra un archivo de bloqueo cuando comienza a ejecutarse, leerá la identificación del proceso del archivo de bloqueo y comprobará si ese proceso existe. Si el proceso no existe, eliminará el archivo de bloqueo obsoleto y continuará. Y solo si el archivo de bloqueo existe Y el proceso aún se está ejecutando, saldrá. Y escribe un mensaje cuando sale.
# lock to ensure we don''t get two copies of the same job
script_name="myscript.sh"
lock="/var/run/${script_name}.pid"
if [[ -e "${lock}" ]]; then
pid=$(cat ${lock})
if [[ -e /proc/${pid} ]]; then
echo "${script_name}: Process ${pid} is still running, exiting."
exit 1
else
# Clean up previous lock file
rm -f ${lock}
fi
fi
trap "rm -f ${lock}; exit $?" INT TERM EXIT
# write $$ (PID) to the lock file
echo "$$" > ${lock}
Creo que el flock
es probablemente la variante más fácil (y más memorable). Lo uso en un trabajo cron para autocodificar dvds y cds
# try to run a command, but fail immediately if it''s already running
flock -n /var/lock/myjob.lock my_bash_command
Use -w
para los tiempos de espera o las opciones de dejar de esperar hasta que se libere el bloqueo. Finalmente, la página man muestra un buen ejemplo para múltiples comandos:
(
flock -n 9 || exit 1
# ... commands executed under lock ...
) 9>/var/lock/mylockfile
El bloqueo de asesoramiento se ha usado por años y se puede usar en scripts bash. Prefiero el simple flock
(de util-linux[-ng]
) sobre el lockfile
(de procmail
). Y siempre recuerde que una trampa en la salida (sigspec == EXIT
o 0
, atrapando señales específicas es superflua) en esos scripts.
En 2009, lancé mi repetitivo script bloqueable (originalmente disponible en mi página wiki, actualmente disponible como gist ). Transformar eso en una sola instancia por usuario es trivial. Utilizándolo también puede escribir fácilmente scripts para otros escenarios que requieran algún bloqueo o sincronización.
Aquí está la repetición mencionada para su conveniencia.
#!/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.
Encontré esto en las dependencias del paquete procmail:
apt install liblockfile-bin
Para ejecutar: dotlockfile -l file.lock
file.lock se creará.
Para desbloquear: dotlockfile -u file.lock
Use esto para listar este paquete de archivos / comando: dpkg-query -L liblockfile-bin
Encontré una manera bastante simple de manejar "una copia de script por sistema". Sin embargo, no me permite ejecutar varias copias del script desde muchas cuentas (en Linux estándar).
Solución:
Al comienzo del guión, di:
pidof -s -o ''%PPID'' -x $( basename $0 ) > /dev/null 2>&1 && exit
Aparentemente pidof funciona muy bien de una manera que:
- no tiene límite en el nombre del programa como
ps -C ...
- no requiere que haga
grep -v grep
(o algo similar)
Y no depende de los archivos de bloqueo, que para mí es una gran victoria, porque transmitirlos significa que tienes que agregar el manejo de archivos de bloqueo obsoletos, lo cual no es realmente complicado, pero si se puede evitar, ¿por qué no?
En cuanto a verificar con "una copia de script por usuario en ejecución", escribí esto, pero no estoy muy contento con él:
(
pidof -s -o ''%PPID'' -x $( basename $0 ) | tr '' '' ''/n''
ps xo pid= | tr -cd ''[0-9/n]''
) | sort | uniq -d
y luego verifico su salida, si está vacía, no hay copias del script del mismo usuario.
Las distribuciones Ubuntu / Debian tienen la herramienta start-stop-daemon
, que es para el mismo propósito que usted describe. Consulte también /etc/init.d/skeleton para ver cómo se usa al escribir scripts de inicio / detención.
- Noah
No estoy seguro de que haya una solución robusta de una sola línea, por lo que es posible que termine de hacer la suya propia.
Los archivos de bloqueo son imperfectos, pero menos que el uso de ''ps | grep | grep -v ''pipelines.
Una vez dicho esto, puede considerar mantener el control del proceso separado de su secuencia de comandos: tener un script de inicio. O, al menos, factorícela en funciones que se encuentran en un archivo separado, por lo que puede que en el script de llamadas tenga:
. 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
en cada script que necesita la lógica de control. La trap garantiza que el archivo de bloqueo se elimine cuando la persona que llama sale, por lo que no tiene que codificar esto en cada punto de salida en el script.
Utilizar un script de control separado significa que puede verificar la cordura en casos límite: eliminar archivos de registro obsoletos, verificar que el archivo de bloqueo esté asociado correctamente con una instancia del script en ejecución actualmente, dar una opción para matar el proceso en ejecución, y así sucesivamente. También significa que tienes una mejor oportunidad de usar grep en la salida de ps
éxito. Un ps-grep se puede usar para verificar que un archivo de bloqueo tenga un proceso en ejecución asociado. Tal vez podría nombrar sus archivos de bloqueo de alguna manera para incluir información sobre el proceso: usuario, pid, etc., que puede ser utilizado por una invocación de script posterior para decidir si el proceso que creó el archivo de bloqueo todavía existe.
Si el script es el mismo para todos los usuarios, puede usar un enfoque de lockfile
. Si adquiere el candado, proceda de otra manera, muestre un mensaje y salga.
Como ejemplo:
[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] $
Después de que se haya adquirido /tmp/the.lock
su script será el único con acceso a la ejecución. Cuando termines, solo quita el candado. En forma de script, esto podría verse así:
#!/bin/bash
lockfile -r 0 /tmp/the.lock || exit 1
# Do stuff here
rm -f /tmp/the.lock
También recomendaría mirar chpst (parte de runit):
chpst -L /tmp/your-lockfile.loc ./script.name.sh
Tuve el mismo problema, y se me ocurrió una template que utiliza archivo de bloqueo, un archivo pid que contiene el número de identificación del proceso y una comprobación kill -0 $(cat $pid_file)
para hacer que las secuencias de comandos canceladas no detengan la siguiente ejecución. Esto crea una carpeta foobar- $ USERID en / tmp donde vive el archivo lock y el archivo pid.
Todavía puede llamar al script y hacer otras cosas, siempre y cuando mantenga esas acciones en 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
Una solución definitiva de una línea:
[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"
desde con su script:
ps -ef | grep $0 | grep $(whoami)