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 -0que no entrega ninguna señal, sino que solo comprueba si existe un proceso con el PID dado. Además, la llamada a trapgarantizará que el archivo de bloqueo se elimine incluso cuando se finalice su proceso (excepto kill -9).
Como ya se mencionó en un comentario sobre otra respuesta, esto tiene un defecto fatal: si la otra secuencia de comandos se inicia entre la verificación y el eco, eres un brindis.
Paul Tomblin
1
El truco del enlace simbólico es bueno, pero si el propietario del archivo de bloqueo es kill -9'd o el sistema falla, todavía hay una condición de carrera para leer el enlace simbólico, observe que el propietario se fue y luego elimínelo. Me quedo con mi solución.
bmdhacks
9
La comprobación y creación atómica está disponible en el shell utilizando flock (1) o lockfile (1). Ver otras respuestas.
dmckee --- ex gatito moderador
3
Vea mi respuesta para una forma portátil de hacer una verificación atómica y crear sin tener que depender de utilidades como flock o lockfile.
lhunath
2
Esto no es atómico y, por lo tanto, es inútil. Necesita un mecanismo atómico para probar y configurar.
K Richard Pixley
214
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 10200|| 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.
¿Qué es el 200? Dice "fd" en el manul, pero no sé qué significa eso.
chovy
44
@chovy "descriptor de archivo", un identificador entero que designa un archivo abierto.
Alex B
66
Si alguien más se pregunta: la sintaxis ( command A ) command Binvoca 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.
Dr. Jan-Philip Gehrcke
1
Creo que el código dentro del subconjunto debería ser más parecido a: if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fisi 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?).
Jonathan Leffler
1
El archivo redirigido a es solo una carpeta de lugar para que actúe el bloqueo, no hay datos significativos que entren en él. El exites de la parte dentro del (). Cuando finaliza el subproceso, el bloqueo se libera automáticamente, porque no hay ningún proceso que lo retenga.
clacke
158
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. mkdircrea 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.
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>&-return1# Locked by a pid.done}
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) .
Puede combinar la if ! mkdirparte 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.
Tobias Kienzler
44
Es cierto que mkdirno 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 /tmpque será un recurso compartido de NFS y probablemente será proporcionado por un fs que se implementa mkdiratómicamente.
lhunath
55
Pero hay una manera de verificar la existencia de un archivo normal y crearlo atómicamente si no lo hace: usar lnpara 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.
Juan Céspedes
44
No es 'una manera de comprobar si existe un archivo y crear en una sola acción atómica' - es 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 ).
Toby Speight
55
"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". Para hacerlo atómico, debe hacerse en el nivel del kernel - y se hace a nivel del kernel con flock (1) linux.die.net/man/1/flock que parece tener la fecha de copyright del hombre desde al menos 2006. Así que hice un voto negativo (- 1), nada personal, solo tengo la firme convicción de que usar las herramientas implementadas por el kernel proporcionadas por los desarrolladores del kernel es correcto.
Craig Hicks
42
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.
La flock()llamada al sistema no es POSIX y no funciona para archivos en montajes NFS.
maxschlepzig
17
Ejecutando desde un trabajo Cron que uso flock -x -n %lock file% -c "%command%"para asegurarme de que solo se ejecute una instancia.
Ryall
Aww, en lugar de la bandada poco imaginativa (1) deberían haber ido con algo como la bandada (U). ... tiene algo de familiaridad. . Parece que he escuchado eso antes de una o dos veces.
Kent Kruckeberg
Es notable que la documentación de flock (2) especifica el uso solo con archivos, pero la documentación de flock (1) especifica el uso con archivos o directorios. La documentación de flock (1) no es explícita sobre cómo indicar la diferencia durante la creación, pero supongo que se hace agregando un "/" final. De todos modos, si flock (1) puede manejar directorios pero flock (2) no, entonces flock (1) no se implementa solo en flock (2).
Craig Hicks
27
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.
Ejecute esto varias veces al mismo tiempo (como "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ") y la secuencia de comandos se filtrará varias veces.
Nippysaurus
77
@Nippysaurus: este método de bloqueo no tiene fugas. Lo que vio fue que la secuencia de comandos inicial terminaba antes de que se lanzaran todas las copias, por lo que otra pudo (correctamente) obtener el bloqueo. Para evitar este falso positivo, agregue un sleep 10antes rmdire intente volver a conectarse en cascada; nada se "escapará".
Sir Athos
Otras fuentes afirman que mkdir no es atómico en algunos sistemas de archivos como NFS. Y, por cierto, he visto ocasiones en las que, en NFS, mkdir recursivo concurrente conduce a errores a veces con trabajos de matriz jenkins. Así que estoy bastante seguro de que ese es el caso. Pero mkdir es bastante bueno para los casos de uso menos exigentes de la OMI.
akostadinov
Puede usar la opción Bash'es noclobber con archivos normales.
Palec
26
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:
enlace simbólico
rebautizar
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.
¿Qué páginas de NFS Illustrated respaldan la afirmación de que mkdir no es atómico sobre NFS?
maxschlepzig
Gracias por esta técnica. Una implementación de shell mutex está disponible en mi nuevo shell lib: github.com/Offirmo/offirmo-shell-lib , vea "mutex". Utiliza lockfilesi está disponible, o recurre a este symlinkmétodo si no.
Offirmo
Agradable. Lamentablemente, este método no proporciona una forma de eliminar automáticamente bloqueos obsoletos.
Richard Hansen
Para el desbloqueo de dos etapas ( mv, rm), ¿ rm -fse debe usar, en lugar de rmen 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 mvy rm), finalmente P1 intenta rmy falla.
Matt Wallis
1
@MattWallis Ese último problema podría mitigarse fácilmente si se incluye $$en el ${f}.deletemenombre del archivo.
Stefan Majewsky
23
Otra opción es usar la noclobberopció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 belowelse
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:
Enfoque muy novedoso ... esta parece ser una forma de lograr la atomicidad utilizando un archivo de bloqueo en lugar de un directorio de bloqueo.
Matt Caldwell
Buen enfoque. :-) En la trampa EXIT, debería restringir qué proceso puede limpiar el archivo de bloqueo. Por ejemplo: trap 'if [[$ (cat "$ lockfile") == "$$"]]; entonces rm "$ lockfile"; fi 'EXIT
Kevin Seifert
1
Los archivos de bloqueo no son atómicos sobre NFS. Es por eso que la gente se movió a usar directorios de bloqueo.
K Richard Pixley
20
Puede usar GNU Parallelesto 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.
Lástima que las personas sean reacias a rechazar las respuestas inútiles: esto lleva a que nuevas respuestas relevantes sean enterradas en una pila de basura.
Dmitry Grigoryev
44
Solo necesitamos muchos votos a favor. Esta es una respuesta tan ordenada y poco conocida. (¡Aunque para ser pedante, OP quería algo rápido y sucio mientras que esto es rápido y limpio!) Más información semen la pregunta relacionada unix.stackexchange.com/a/322200/199525 .
Parcialmente nublado
16
Para los scripts de shell, tiendo a ir con el mkdirover flockya que hace que los bloqueos sean más portátiles.
De cualquier manera, usar set -eno 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 runningif $(mkdir $LOCK_DIR)then
mkdir $__lockdir # create this instance's specific lock in queue
LOCK_EXISTS=true # Globalelse
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 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 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_exitsiempre 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.
Su guión es demasiado detallado, creo que podría haber sido mucho más corto, pero en general, sí, debe configurar trampas para hacerlo correctamente. También agregaría SIGHUP.
mojuba
Esto funciona bien, excepto que parece verificar $ LOCK_DIR mientras que elimina $ __ lockdir. ¿Tal vez debería sugerir que al eliminar el bloqueo hagas rm -r $ LOCK_DIR?
bevada
Gracias por la sugerencia. Lo anterior se levantó el código y se colocó en forma de código psuedo, por lo que necesitará ajustes según el uso de la gente. Sin embargo, deliberadamente fui con rmdir en mi caso, ya que rmdir elimina de forma segura los directorios solo si están vacíos. Si la gente está colocando recursos en ellos, como archivos PID, etc., deben alterar la limpieza de su cerradura para que sea más agresiva rm -r $LOCK_DIRo 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.
Mark Stinson
¿Has probado exit 1002?
Gilles Quenot
13
¿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. :)
¿Cómo simulo esto para probarlo? ¿Hay alguna manera de iniciar un script dos veces en una línea y tal vez recibir una advertencia, si ya se está ejecutando?
rubo77
2
¡Esto no está funcionando en absoluto! ¿Por qué verificar -gt 2? grep no siempre se encuentra en el resultado de ps!
rubo77
pgrepno está en POSIX. Si desea que esto funcione de forma portátil, necesita POSIX psy procesar su salida.
Palec
En OSX -cno existe, tendrá que usar | wc -l. Acerca de la comparación de números: -gt 1se verifica ya que la primera instancia se ve a sí misma.
Benjamin Peter
6
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 1else# lock successfully acquired, save PID
echo $$ > $PIDFILE
fi
trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT
echo hello
sleep 30s
echo bye
¿Crear un archivo de bloqueo en una ubicación conocida y verificar la existencia al inicio del script? Poner el PID en el archivo puede ser útil si alguien intenta rastrear una instancia errónea que impide la ejecución del script.
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 10200if["$?"!="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
Puede usar ln -s, porque esto puede crear un enlace simbólico solo cuando no exista ningún archivo o enlace simbólico, igual que mkdir. muchos procesos del sistema usaban enlaces simbólicos en el pasado, por ejemplo init o inetd. synlink mantiene la identificación del proceso, pero realmente no señala nada. Por los años este comportamiento fue cambiado. procesos utiliza bandadas y semáforos.
Znik
5
Las respuestas existentes publicadas dependen de la utilidad CLI flocko 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>&2return1fi
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>&2if test -f "${LOCK}";then
echo "current lock information held by: $(cat "${LOCK}")"1>&2fireturn2fi
rm -f "${TMPLOCK}"return0;};
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 1fi
trap clean_up SIGHUP SIGINT SIGTERM
# bulk of script
clean_up
exit 0# end of script
Recuerde llamar clean_upa cualquier punto de salida en su secuencia de comandos.
Al apuntar a una máquina Debian, encuentro que el lockfile-progspaquete es una buena solución. procmailTambién viene con una lockfileherramienta. Sin embargo, a veces estoy atrapado con ninguno de estos.
Aquí está mi solución que usa mkdiratomic-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_requirecuando 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_tryy 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"]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 process
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return1else# orphaned lock, take it over( echo $$ >"$LOCK_PID_FILE")2>/dev/null &&local LOCK_PID="$$"fifiif["`trap -p EXIT`"!=""]then# already have an EXIT trap
echo "Cannot get lock, already have an EXIT trap"return1fiif["$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 first
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return1fi
trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT
return0# got lock}function exclusive_lock_retry()# [lockname] [retries] [delay]{local LOCK_NAME="$1"local MAX_TRIES="${2:-5}"local DELAY="${3:-2}"local TRIES=0local 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]thenreturn0fidonereturn"$LOCK_RETVAL"}function exclusive_lock_require()# [lockname] [retries] [delay]{if! exclusive_lock_retry "$@"then
exit 1fi}
Gracias, lo probé en cygwin y pasó pruebas simples.
ndemou
4
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
Solo pensé en señalar que -x (bloqueo de escritura) ya está configurado de forma predeterminada.
Keldon Alleyne
y -nserá exit 1de inmediato si no puede obtener el bloqueo
Anentropic
Gracias @ KeldonAlleyne, actualicé el código para eliminar "-x" ya que es el predeterminado.
presto8
3
Algunos tienen Unixes lockfileque 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.
lockfileSe distribuye con procmail. También hay una alternativa dotlockfileque va con el liblockfilepaquete. Ambos afirman que funcionan de manera confiable en NFS.
Mr. Deathless el
3
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 noclobberopció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 pidofporque 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). ifDeclaración más simple , en una línea:
Esto es sensible a la salida 'ps', en mi máquina (Ubuntu 14.04, / bin / ps de procps-ng versión 3.3.9) el comando 'ps axf' imprime caracteres de árbol ascii que interrumpen los números de campo. Esto funcionó para mí: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
qneill
2
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"return0elseif[-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">&2return1else# 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"return0else
echo "Cannot write to $LOCKFILE. Error.">&2return1fifielse
echo "Do you have write permissons to $LOCKFILE ?">&2return1fifi}# 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
Te di +1 para una solución diferente. Aunque tampoco funciona en AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: lista no válida con -o.) No HP-UX (> ps -eo pid, ppid, lstart $$ | cola -1 ps: opción ilegal - o). Gracias.
Tagar
2
Agregue esta línea al comienzo de su secuencia de comandos
["${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 flockutilidad. 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 pspara 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
Sin la llamada de captura (o al menos una limpieza cerca del final para el caso normal), tiene el error de falso positivo donde se deja el archivo de bloqueo después de la última ejecución y el PID ha sido reutilizado por otro proceso más tarde. (Y en el peor de los casos, ha sido dotado para un proceso de larga duración como apache ...)
Philippe Chaintreuil
1
Estoy de acuerdo, mi enfoque es defectuoso, necesita una trampa. He actualizado mi solución. Todavía prefiero no tener dependencias externas.
Filidor Wiese
1
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 1fi
exec 3> $lock_file
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.
Esto puede o no ser un problema, dependiendo de cómo se use, pero hay una condición de carrera entre probar el bloqueo y crearlo, de modo que se puedan iniciar dos scripts al mismo tiempo. Si uno termina primero, el otro permanecerá ejecutándose sin ningún archivo de bloqueo.
TimB
3
C News, que me enseñó mucho sobre las secuencias de comandos de shell portátiles, solía hacer un archivo lock. $$ y luego intentaba vincularlo con "lock"; si el enlace se realizó correctamente, tenía el bloqueo; de lo contrario, eliminó el bloqueo. $$ y salió
Paul Tomblin
Esa es una muy buena manera de hacerlo, excepto que aún sufres la necesidad de eliminar el archivo de bloqueo manualmente si algo sale mal y el archivo de bloqueo no se elimina.
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 :
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 atrap
garantizará que el archivo de bloqueo se elimine incluso cuando se finalice su proceso (exceptokill -9
).fuente
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.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.fuente
( command A ) command B
invoca una subshell paracommand 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.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:
Puede usarlo en un script como este:
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) .
fuente
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íafuser
.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 implementamkdir
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, comolockfile-create
(inlockfile-progs
) odotlockfile
(inliblockfile-bin
). Y asegúrese de limpiar correctamente (ptrap 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.
fuente
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:
Debe cuidar los bloqueos obsoletos, de lo contrario, después de un bloqueo, su script nunca volverá a ejecutarse.
fuente
sleep 10
antesrmdir
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:
bloquear un archivo:
desbloquear el directorio actual (suposición, el proceso en ejecución realmente adquirió el bloqueo):
desbloquear un archivo (suposición, el proceso en ejecución realmente adquirió el bloqueo):
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.
fuente
lockfile
si está disponible, o recurre a estesymlink
método si no.mv
,rm
), ¿rm -f
se debe usar, en lugar derm
en el caso de que dos procesos P1, P2 estén corriendo? Por ejemplo, P1 comienza a desbloquear conmv
, luego P2 bloquea, luego P2 desbloquea (ambosmv
yrm
), finalmente P1 intentarm
y falla.$$
en el${f}.deleteme
nombre del archivo.Otra opción es usar la
noclobber
opción de shell ejecutandoset -C
. Entonces>
fallará si el archivo ya existe.En breve:
Esto hace que el shell llame:
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:Es interesante que
pdksh
agregue laO_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.
Eliminar en cualquier salida
Se ejecutan nuevas ejecuciones siempre que el script no se esté ejecutando.
fuente
Puede usar
GNU Parallel
esto ya que funciona como un mutex cuando se llama comosem
. Entonces, en términos concretos, puede usar:Si también quieres un tiempo de espera, usa:
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.fuente
sem
en la pregunta relacionada unix.stackexchange.com/a/322200/199525 .Para los scripts de shell, tiendo a ir con el
mkdir
overflock
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):
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.
fuente
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á:
Por supuesto, solo asegúrese de que el nombre de su script sea único. :)
fuente
-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 POSIXps
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.
fuente
¿Crear un archivo de bloqueo en una ubicación conocida y verificar la existencia al inicio del script? Poner el PID en el archivo puede ser útil si alguien intenta rastrear una instancia errónea que impide la ejecución del script.
fuente
Este ejemplo se explica en la bandada de hombres, pero necesita algunas mejoras, porque debemos manejar los errores y los códigos de salida:
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
fuente
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)
omkemp(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:El siguiente es un ejemplo de cómo usar la función de bloqueo:
Recuerde llamar
clean_up
a cualquier punto de salida en su secuencia de comandos.He usado lo anterior en entornos Linux y FreeBSD.
fuente
Al apuntar a una máquina Debian, encuentro que el
lockfile-progs
paquete es una buena solución.procmail
También viene con unalockfile
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
yexclusive_lock_retry
) si necesita algo más complejo.fuente
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:
fuente
-n
seráexit 1
de inmediato si no puede obtener el bloqueoAlgunos tienen Unixes
lockfile
que es muy similar a los ya mencionadosflock
.Desde la página del manual:
fuente
lockfile
utilidad?lockfile
Se distribuye conprocmail
. También hay una alternativadotlockfile
que va con elliblockfile
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:
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
fuente
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:fuente
/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.
fuente
Agregue esta línea al comienzo de su secuencia de comandos
Es un código repetitivo de Man Flock.
Si desea más registros, use este
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.
fuente
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.fuente
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:
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.
fuente
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).
fuente
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.
fuente
Respondió un millón de veces ya, pero de otra manera, sin la necesidad de dependencias externas:
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.
fuente
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:
fuente
Yo uso oneliner @ al comienzo del 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í.
fuente
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.
fuente
¿Rápido y sucio?
fuente