Respuestas:
Aquí hay una implementación que usa un archivo de bloqueo y hace eco de un PID. Esto sirve como protección si el proceso se termina antes de eliminar el archivo pid :
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}
El truco aquí es el kill -0
que no entrega ninguna señal, sino que solo comprueba si existe un proceso con el PID dado. Además, la llamada a trap
garantizará que el archivo de bloqueo se elimine incluso cuando se finalice su proceso (excepto kill -9
).
Utilícelo flock(1)
para hacer un bloqueo exclusivo con ámbito en un descriptor de archivo. De esta manera, incluso puede sincronizar diferentes partes del 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
Esto garantiza que el código entre (
y )
se ejecute solo por un proceso a la vez y que el proceso no espere demasiado para un bloqueo.
Advertencia: este comando en particular es parte de util-linux
. Si ejecuta un sistema operativo que no sea Linux, puede o no estar disponible.
( command A ) command B
invoca una subshell para command A
. Documentado en tldp.org/LDP/abs/html/subshells.html . Todavía no estoy seguro sobre el momento de invocación de la subshell y el comando B.
if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
si el tiempo de espera se produce (algún otro proceso tiene el archivo bloqueado), este script no continúa y modifica el archivo. Probablemente ... el contraargumento es 'pero si ha tomado 10 segundos y el bloqueo aún no está disponible, nunca estará disponible', presumiblemente porque el proceso que mantiene el bloqueo no está finalizando (tal vez se está ejecutando bajo un depurador?).
exit
es de la parte dentro del (
)
. Cuando finaliza el subproceso, el bloqueo se libera automáticamente, porque no hay ningún proceso que lo retenga.
Todos los enfoques que prueban la existencia de "archivos de bloqueo" son defectuosos.
¿Por qué? Porque no hay forma de verificar si existe un archivo y crearlo en una sola acción atómica. Debido a esto; hay una condición de carrera que SERÁ hacer sus intentos de rotura exclusión mutua.
En cambio, necesitas usar mkdir
. mkdir
crea un directorio si aún no existe, y si lo hace, establece un código de salida. Más importante aún, hace todo esto en una sola acción atómica que lo hace perfecto para este escenario.
if ! mkdir /tmp/myscript.lock 2>/dev/null; then
echo "Myscript is already running." >&2
exit 1
fi
Para todos los detalles, vea el excelente BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Si desea cuidar los bloqueos obsoletos, el fusor (1) es útil. El único inconveniente aquí es que la operación dura aproximadamente un segundo, por lo que no es instantánea.
Aquí hay una función que escribí una vez que resuelve el problema usando el fusor:
# 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
}
Puede usarlo en un script como este:
mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }
Si no le importa la portabilidad (estas soluciones deberían funcionar en casi cualquier caja UNIX), el fusor de Linux (1) ofrece algunas opciones adicionales y también hay un lote (1) .
if ! mkdir
parte con la comprobación de si el proceso con el PID almacenado (en el inicio exitoso) dentro del lockdir se está ejecutando y es idéntico al script para la protección de stalenes. Esto también protegería contra la reutilización del PID después de un reinicio y ni siquiera lo requeriría fuser
.
mkdir
no se define como una operación atómica y, como tal, el "efecto secundario" es un detalle de implementación del sistema de archivos. Le creo completamente si dice que NFS no lo implementa de manera atómica. Aunque no sospecho /tmp
que será un recurso compartido de NFS y probablemente será proporcionado por un fs que se implementa mkdir
atómicamente.
ln
para crear un enlace duro desde otro archivo. Si tiene sistemas de archivos extraños que no garantizan eso, puede verificar el inodo del nuevo archivo luego para ver si es el mismo que el archivo original.
open(... O_CREAT|O_EXCL)
. Solo necesita un programa de usuario adecuado para hacerlo, como lockfile-create
(in lockfile-progs
) o dotlockfile
(in liblockfile-bin
). Y asegúrese de limpiar correctamente (p trap EXIT
. Ej. ), O pruebe si hay bloqueos rancios (p --use-pid
. Ej. Con ).
Hay un contenedor alrededor de la llamada al sistema flock (2) llamado, sin imaginación, flock (1). Esto hace que sea relativamente fácil obtener bloqueos exclusivos de manera confiable sin preocuparse por la limpieza, etc. Hay ejemplos en la página de manual sobre cómo usarlo en un script de shell.
flock()
llamada al sistema no es POSIX y no funciona para archivos en montajes NFS.
flock -x -n %lock file% -c "%command%"
para asegurarme de que solo se ejecute una instancia.
Necesita una operación atómica, como una bandada, de lo contrario, eventualmente fallará.
Pero qué hacer si el lote no está disponible. Bueno, hay mkdir. Esa también es una operación atómica. Solo un proceso dará como resultado un mkdir exitoso, todos los demás fallarán.
Entonces el código es:
if mkdir /var/lock/.myscript.exclusivelock
then
# do stuff
:
rmdir /var/lock/.myscript.exclusivelock
fi
Debe cuidar los bloqueos obsoletos, de lo contrario, después de un bloqueo, su script nunca volverá a ejecutarse.
sleep 10
antes rmdir
e intente volver a conectarse en cascada; nada se "escapará".
Para que el bloqueo sea confiable, necesita una operación atómica. Muchas de las propuestas anteriores no son atómicas. La utilidad lockfile (1) propuesta parece prometedora como la página de manual mencionada, que es "resistente a NFS". Si su sistema operativo no admite el archivo de bloqueo (1) y su solución tiene que funcionar en NFS, no tiene muchas opciones ...
NFSv2 tiene dos operaciones atómicas:
Con NFSv3, la llamada de creación también es atómica.
Las operaciones de directorio NO son atómicas en NFSv2 y NFSv3 (consulte el libro 'NFS Illustrated' de Brent Callaghan, ISBN 0-201-32570-5; Brent es un veterano de NFS en Sun).
Sabiendo esto, puede implementar spin-locks para archivos y directorios (en shell, no PHP):
bloquear el directorio actual:
while ! ln -s . lock; do :; done
bloquear un archivo:
while ! ln -s ${f} ${f}.lock; do :; done
desbloquear el directorio actual (suposición, el proceso en ejecución realmente adquirió el bloqueo):
mv lock deleteme && rm deleteme
desbloquear un archivo (suposición, el proceso en ejecución realmente adquirió el bloqueo):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Eliminar tampoco es atómico, por lo tanto, primero cambie el nombre (que es atómico) y luego elimine.
Para las llamadas de enlace simbólico y cambio de nombre, ambos nombres de archivo deben residir en el mismo sistema de archivos. Mi propuesta: usar solo nombres de archivo simples (sin rutas) y colocar el archivo y bloquearlo en el mismo directorio.
lockfile
si está disponible, o recurre a este symlink
método si no.
mv
, rm
), ¿ rm -f
se debe usar, en lugar de rm
en el caso de que dos procesos P1, P2 estén corriendo? Por ejemplo, P1 comienza a desbloquear con mv
, luego P2 bloquea, luego P2 desbloquea (ambos mv
y rm
), finalmente P1 intenta rm
y falla.
$$
en el ${f}.deleteme
nombre del archivo.
Otra opción es usar la noclobber
opción de shell ejecutando set -C
. Entonces >
fallará si el archivo ya existe.
En breve:
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
Esto hace que el shell llame:
open(pathname, O_CREAT|O_EXCL)
que crea atómicamente el archivo o falla si el archivo ya existe.
Según un comentario en BashFAQ 045 , esto puede fallar ksh88
, pero funciona en todos mis 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
Es interesante que pdksh
agregue la O_TRUNC
bandera, pero obviamente es redundante:
o estás creando un archivo vacío o no estás haciendo nada.
Cómo lo haga rm
depende de cómo desee que se manejen las salidas impuras.
Eliminar al salir limpio
Las nuevas ejecuciones fallan hasta que se resuelve el problema que causó la salida impura y el archivo de bloqueo se elimina manualmente.
# acquire lock
# do work (code here may call exit, etc.)
rm "$lockfile"
Eliminar en cualquier salida
Se ejecutan nuevas ejecuciones siempre que el script no se esté ejecutando.
trap 'rm "$lockfile"' EXIT
Puede usar GNU Parallel
esto ya que funciona como un mutex cuando se llama como sem
. Entonces, en términos concretos, puede usar:
sem --id SCRIPTSINGLETON yourScript
Si también quieres un tiempo de espera, usa:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
El tiempo de espera de <0 significa salir sin ejecutar el script si el semáforo no se libera dentro del tiempo de espera, el tiempo de espera de> 0 significa ejecutar el script de todos modos.
Tenga en cuenta que debe darle un nombre (con --id
), de lo contrario, el valor predeterminado es el terminal de control.
GNU Parallel
es una instalación muy simple en la mayoría de las plataformas Linux / OSX / Unix, es solo un script de Perl.
sem
en la pregunta relacionada unix.stackexchange.com/a/322200/199525 .
Para los scripts de shell, tiendo a ir con el mkdir
over flock
ya que hace que los bloqueos sean más portátiles.
De cualquier manera, usar set -e
no es suficiente. Eso solo sale del script si falla algún comando. Tus cerraduras aún se quedarán atrás.
Para una limpieza de bloqueo adecuada, realmente debe configurar sus trampas en algo como este código psuedo (levantado, simplificado y no probado pero de secuencias de comandos utilizadas activamente):
#=======================================================================
# 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
Esto es lo que sucederá. Todas las trampas producirán una salida, por lo que la función __sig_exit
siempre sucederá (salvo SIGKILL) que limpiará tus bloqueos.
Nota: mis valores de salida no son valores bajos. ¿Por qué? Varios sistemas de procesamiento por lotes crean o tienen expectativas de los números del 0 al 31. Al establecerlos en otra cosa, puedo hacer que mis secuencias de comandos y secuencias de lotes reaccionen de acuerdo con el trabajo o secuencia de comandos anterior.
rm -r $LOCK_DIR
o incluso forzarla según sea necesario (como también lo he hecho en casos especiales, como la retención de archivos de memoria virtual). Salud.
exit 1002
?
¿Muy rápido y muy sucio? Esta línea en la parte superior de su script funcionará:
[[ $(pgrep -c "`basename \"$0\"`") -gt 1 ]] && exit
Por supuesto, solo asegúrese de que el nombre de su script sea único. :)
-gt 2
? grep no siempre se encuentra en el resultado de ps!
pgrep
no está en POSIX. Si desea que esto funcione de forma portátil, necesita POSIX ps
y procesar su salida.
-c
no existe, tendrá que usar | wc -l
. Acerca de la comparación de números: -gt 1
se verifica ya que la primera instancia se ve a sí misma.
Aquí hay un enfoque que combina el bloqueo de directorio atómico con una comprobación de bloqueo obsoleto a través de PID y reiniciar si está obsoleto. Además, esto no depende de ningún basismo.
#!/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
Este ejemplo se explica en la bandada de hombres, pero necesita algunas mejoras, porque debemos manejar los errores y los códigos de salida:
#!/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
Puede usar otro método, enumerar los procesos que usé en el pasado. Pero esto es más complicado que el método anterior. Debe enumerar los procesos por ps, filtrar por su nombre, filtro adicional grep -v grep para eliminar el parásito y finalmente contarlo por grep -c. y compara con el número. Es complicado e incierto
Las respuestas existentes publicadas dependen de la utilidad CLI flock
o no protegen adecuadamente el archivo de bloqueo. La utilidad flock no está disponible en todos los sistemas que no son Linux (es decir, FreeBSD), y no funciona correctamente en NFS.
En mis primeros días de administración del sistema y desarrollo del sistema, me dijeron que un método seguro y relativamente portátil para crear un archivo de bloqueo era crear un archivo temporal utilizando mkemp(3)
o mkemp(1)
, escribir información de identificación en el archivo temporal (es decir, PID), luego enlace duro el archivo temporal al archivo de bloqueo. Si el enlace fue exitoso, entonces ha obtenido con éxito el bloqueo.
Cuando uso bloqueos en scripts de shell, normalmente coloco una obtain_lock()
función en un perfil compartido y luego la obtengo de los scripts. A continuación se muestra un ejemplo de mi función de bloqueo:
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;
};
El siguiente es un ejemplo de cómo usar la función de bloqueo:
#!/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
Recuerde llamar clean_up
a cualquier punto de salida en su secuencia de comandos.
He usado lo anterior en entornos Linux y FreeBSD.
Al apuntar a una máquina Debian, encuentro que el lockfile-progs
paquete es una buena solución. procmail
También viene con una lockfile
herramienta. Sin embargo, a veces estoy atrapado con ninguno de estos.
Aquí está mi solución que usa mkdir
atomic-ness y un archivo PID para detectar bloqueos obsoletos. Este código está actualmente en producción en una configuración de Cygwin y funciona bien.
Para usarlo simplemente llame exclusive_lock_require
cuando necesite obtener acceso exclusivo a algo. Un parámetro de nombre de bloqueo opcional le permite compartir bloqueos entre diferentes scripts. También hay dos funciones de nivel inferior ( exclusive_lock_try
y exclusive_lock_retry
) si necesita algo más complejo.
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
}
Si las limitaciones de la bandada, que ya se han descrito en otra parte de este hilo, no son un problema para usted, entonces esto debería funcionar:
#!/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
-n
será exit 1
de inmediato si no puede obtener el bloqueo
Algunos tienen Unixes lockfile
que es muy similar a los ya mencionados flock
.
Desde la página del manual:
lockfile se puede usar para crear uno o más archivos de semáforos. Si el archivo de bloqueo no puede crear todos los archivos especificados (en el orden especificado), espera el tiempo de sueño (el valor predeterminado es 8) segundos y vuelve a intentar el último archivo que no tuvo éxito. Puede especificar el número de reintentos a realizar hasta que se devuelva el error. Si el número de reintentos es -1 (predeterminado, es decir, -r-1), lockfile volverá a intentarlo para siempre.
lockfile
utilidad?
lockfile
Se distribuye con procmail
. También hay una alternativa dotlockfile
que va con el liblockfile
paquete. Ambos afirman que funcionan de manera confiable en NFS.
En realidad, aunque la respuesta de bmdhacks es casi buena, existe una pequeña posibilidad de que el segundo script se ejecute después de comprobar primero el archivo de bloqueo y antes de escribirlo. Entonces ambos escribirán el archivo de bloqueo y ambos se ejecutarán. Aquí es cómo hacer que funcione con seguridad:
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
La noclobber
opción asegurará que el comando de redireccionamiento fallará si el archivo ya existe. Entonces, el comando de redirección es en realidad atómico: usted escribe y verifica el archivo con un comando. No necesita eliminar el archivo de bloqueo al final del archivo: la trampa lo eliminará. Espero que esto ayude a las personas que lo leerán más tarde.
PD: No vi que Mikel ya respondió la pregunta correctamente, aunque no incluyó el comando trap para reducir la posibilidad de que quede el archivo de bloqueo después de detener el script con Ctrl-C, por ejemplo. Entonces esta es la solución completa
Quería eliminar los archivos de bloqueo, los lockdirs, los programas de bloqueo especiales e incluso pidof
porque no se encuentra en todas las instalaciones de Linux. También quería tener el código más simple posible (o al menos la menor cantidad de líneas posible). if
Declaración más simple , en una línea:
if [[ $(ps axf | awk -v pid=$$ '$1!=pid && $6~/'$(basename $0)'/{print $1}') ]]; then echo "Already running"; exit; fi
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Utilizo un enfoque simple que maneja los archivos de bloqueo obsoletos.
Tenga en cuenta que algunas de las soluciones anteriores que almacenan el pid, ignoran el hecho de que el pid puede ajustarse. Entonces, simplemente verificar si hay un proceso válido con el pid almacenado no es suficiente, especialmente para los scripts de larga ejecución.
Uso noclobber para asegurarme de que solo se pueda abrir un script y escribir en el archivo de bloqueo a la vez. Además, almaceno suficiente información para identificar de forma exclusiva un proceso en el archivo de bloqueo. Defino el conjunto de datos para identificar unívocamente un proceso para que sea pid, ppid, lstart.
Cuando se inicia una nueva secuencia de comandos, si no puede crear el archivo de bloqueo, verifica que el proceso que creó el archivo de bloqueo todavía está presente. Si no, asumimos que el proceso original tuvo una muerte sin gracia y dejó un archivo de bloqueo obsoleto. El nuevo script toma posesión del archivo de bloqueo, y todo vuelve a estar bien en el mundo.
Debería funcionar con múltiples shells en múltiples plataformas. Rápido, portátil y 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
Agregue esta línea al comienzo de su secuencia de comandos
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
Es un código repetitivo de Man Flock.
Si desea más registros, use este
[ "${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."
Esto establece y verifica los bloqueos utilizando la flock
utilidad. Este código detecta si se ejecutó por primera vez comprobando la variable FLOCKER, si no está configurado con el nombre del script, luego intenta iniciar el script nuevamente de forma recursiva usando flock y con la variable FLOCKER inicializada, si FLOCKER está configurado correctamente, luego flock en la iteración anterior tuvo éxito y está bien proceder. Si el bloqueo está ocupado, falla con el código de salida configurable.
Parece que no funciona en Debian 7, pero parece funcionar de nuevo con el paquete experimental util-linux 2.25. Escribe "rebaño: ... Archivo de texto ocupado". Podría anularse deshabilitando el permiso de escritura en su secuencia de comandos.
PID y archivos de bloqueo son definitivamente los más confiables. Cuando intenta ejecutar el programa, puede verificar el archivo de bloqueo que, y si existe, puede usar ps
para ver si el proceso aún se está ejecutando. Si no es así, el script puede comenzar, actualizando el PID en el archivo de bloqueo a su propio.
Encuentro que la solución de bmdhack es la más práctica, al menos para mi caso de uso. El uso de flock y lockfile se basa en eliminar el lockfile usando rm cuando finaliza el script, lo que no siempre se puede garantizar (por ejemplo, kill -9).
Cambiaría una cosa menor acerca de la solución de bmdhack: se encarga de eliminar el archivo de bloqueo, sin afirmar que esto es innecesario para el funcionamiento seguro de este semáforo. Su uso de kill -0 asegura que un antiguo archivo de bloqueo para un proceso muerto simplemente se ignorará / se sobrescribirá.
Por lo tanto, mi solución simplificada es simplemente agregar lo siguiente a la parte superior de su 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}
Por supuesto, este script todavía tiene la falla de que los procesos que probablemente comiencen al mismo tiempo tienen un riesgo de carrera, ya que la prueba de bloqueo y las operaciones de configuración no son una sola acción atómica. Pero la solución propuesta para esto por parte de lhunath de usar mkdir tiene la falla de que una secuencia de comandos eliminada puede dejar atrás el directorio, evitando así que se ejecuten otras instancias.
Los semafóricas usos de servicios públicos flock
(como se discutió anteriormente, por ejemplo por presto8) para implementar un semáforo de conteo . Permite cualquier número específico de procesos concurrentes que desee. Lo usamos para limitar el nivel de concurrencia de varios procesos de trabajo en cola.
Es como sem pero mucho más ligero. (Divulgación completa: lo escribí después de encontrar que el sem era demasiado pesado para nuestras necesidades y no había una simple utilidad de conteo de semáforos disponible).
Un ejemplo con flock (1) pero sin subshell. El archivo flock () ed / tmp / foo nunca se elimina, pero eso no importa, ya que obtiene flock () y 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
Respondió un millón de veces ya, pero de otra manera, sin la necesidad de dependencias externas:
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
Cada vez que escribe el PID actual ($$) en el archivo de bloqueo y al iniciar el script, comprueba si un proceso se está ejecutando con el último PID.
Usar el bloqueo del proceso es mucho más fuerte y también se ocupa de las salidas ingratas. lock_file se mantiene abierto mientras se ejecuta el proceso. Se cerrará (por shell) una vez que el proceso exista (incluso si se mata). Encontré que esto es muy eficiente:
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
Yo uso oneliner @ al comienzo del 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
Es bueno ver la presencia de proceso en la memoria (no importa cuál sea el estado del proceso); Pero hace el trabajo por mí.
El camino del rebaño es el camino a seguir. Piensa en lo que sucede cuando el guión muere repentinamente. En el caso de la bandada, simplemente se pierde la bandada, pero eso no es un problema. Además, tenga en cuenta que un truco malvado es tomar una bandada en el script en sí mismo ... pero eso, por supuesto, le permite correr a toda máquina en problemas de permisos.
¿Rápido y sucio?
#!/bin/sh
if [ -f sometempfile ]
echo "Already running... will now terminate."
exit
else
touch sometempfile
fi
..do what you want here..
rm sometempfile