scripts script resueltos programas programacion paso pasar parametros hacer ejercicios ejemplos ejecutar como bash shell lockfile

resueltos - scripts bash ejemplos



Manera rĂ¡pida y sucia de garantizar que solo se ejecute una instancia de un script de shell a la vez (30)

Quería acabar con ficheros de bloqueo, lockdirs, programas especiales de bloqueo e incluso pidofya que no se encuentra en todas las instalaciones de Linux. También quería tener el código más simple posible (o al menos tan pocas líneas como sea posible). Más simple ifdeclaración, en una línea:

if [[ $(ps axf | awk -v pid=$$ ''$1!=pid && $6~/''$(basename $0)''/{print $1}'') ]]; then echo "Already running"; exit; fi

¿Cuál es una forma rápida y sucia de asegurarse de que solo se ejecute una instancia de un script de shell en un momento dado?


Echar un vistazo a FLOM (Free Lock Manager) http://sourceforge.net/projects/flom/ : puede sincronizar comandos y / o secuencias de comandos que utilizan recursos abstractos que no necesitan archivos de bloqueo en un sistema de archivos. Puede sincronizar los comandos que se ejecutan en diferentes sistemas sin un NAS (Network Attached Storage) como un servidor NFS (Network File System).

Usando el caso de uso más simple, la serialización "comando1" y "comando2" puede ser tan fácil como ejecutar:

flom -- command1

y

flom -- command2

a partir de dos secuencias de comandos shell diferentes.


En realidad, aunque la respuesta de bmdhacks es casi buena, hay una pequeña posibilidad de que el segundo script se ejecute después de que primero verifique el archivo de bloqueo y antes de que lo haya escrito. 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 opción noclobber se asegurará de que el comando de redirección falle si el archivo ya existe. Entonces, el comando de redirección es atómico: usted escribe y verifica el archivo con un comando. No es necesario que elimine el archivo de bloqueo al final del archivo, ya que será eliminado por la trampa. Espero que esto ayude a las personas que lo leerán más tarde.

PD. No veo que Mikel ya haya respondido la pregunta correctamente, aunque no incluyó el comando de captura para reducir la posibilidad de que el archivo de bloqueo quede después de detener el script con Ctrl-C, por ejemplo. Entonces esta es la solución completa


El camino rebaño es el camino a seguir. Piense en lo que sucede cuando el guión muere repentinamente. En el rebaño caso que acaba de perder el rebaño, pero eso no es un problema. Además, tenga en cuenta que un mal truco es tomar un rebaño en el guión en sí .. pero por supuesto que le permite ejecutar-vapor a toda máquina en problemas de permisos.


Algunos unixes tienen un lockfile que es muy similar a la flock ya mencionada.

De la página de manual:

lockfile se puede usar para crear uno o más archivos de semáforo. Si lock-file no puede crear todos los archivos especificados (en el orden especificado), espera lateptime (predeterminado en 8) segundos y reintenta el último archivo que no tuvo éxito. Puede especificar la cantidad de reintentos que se deben realizar hasta que se devuelva el error. Si el número de reintentos es -1 (por defecto, es decir, -r-1) el archivo de bloqueo volverá a intentarlo para siempre.


Crear un archivo de bloqueo en una ubicación conocida y comprobar la existencia en el inicio del script? Poner el PID en el archivo puede ser útil si alguien intenta rastrear una instancia errante que impida la ejecución del script.


Hay una envoltura alrededor de la llamada al sistema flock (2) llamada, inimaginablemente, flock (1). Esto hace que sea relativamente fácil obtener bloqueos exclusivos sin preocuparse por la limpieza, etc. Hay ejemplos en la página de manual sobre cómo usarlo en un script de shell.


PID y archivos de bloqueo son definitivamente los más confiables. Cuando intenta ejecutar el programa, puede verificar el archivo de bloqueo, y si existe, puede usar ps para ver si el proceso aún se está ejecutando. Si no es así, la secuencia de comandos puede comenzar, actualizando el PID en el archivo de bloqueo al suyo.


Para que el bloqueo sea confiable, necesita una operación atómica. Muchas de las propuestas anteriores no son atómicas. La utilidad propuesta de lockfile (1) parece prometedora ya que se menciona la página de manual, que es "resistente a NFS". Si su sistema operativo no es compatible con 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 del 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 bloqueos giratorios para archivos y directorios (en shell, no en 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 (supuesto, el proceso de ejecución realmente adquirió el bloqueo):

mv lock deleteme && rm deleteme

desbloquear un archivo (supuesto, el proceso de 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 simbólicas y de cambio de nombre, ambos nombres de archivo deben residir en el mismo sistema de archivos. Mi propuesta: use solo nombres de archivo simples (sin rutas) y coloque el archivo y el candado en el mismo directorio.


Cuando selecciono una máquina Debian, creo que el paquete lockfile-progs es una buena solución. procmail también viene con una herramienta de lockfile . Sin embargo, a veces no estoy atrapado en ninguno de estos.

Aquí está mi solución que usa mkdir para 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 a exclusive_lock_require cuando necesite 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 }


Necesitas una operación atómica, como una bandada, de lo contrario esto eventualmente fallará.

Pero qué hacer si el rebaño no está disponible. Bueno, ahí está mkdir. Esa es una operación atómica también. 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

Debes ocuparte de los bloqueos obsoletos. De lo contrario, el script nunca volverá a ejecutarse.


Encuentro que la solución de bmdhack es la más práctica, al menos para mi caso de uso. Usando rebaño y LockFile confiar en la eliminación de la lockfile usando rm cuando el guión termina, lo que no siempre se puede garantizar (por ejemplo, matar -9).

Me gustaría cambiar una cosa de menor importancia sobre la solución de bmdhack: Se hace un punto de eliminar el archivo de bloqueo, sin que indica que esto no es necesario para el funcionamiento seguro de este semáforo. Su uso de kill -0 asegura que un viejo fichero de bloqueo para un proceso muerto simplemente se ignorará / sobre-escrita.

Por lo tanto, mi solución simplificada es simplemente añadir lo siguiente a la parte superior de su producto único:

## 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 el defecto de que los procesos que son propensos a comenzar al mismo tiempo tienen un riesgo de raza, como la prueba de bloqueo y las operaciones establecidas no son una sola acción atómica. Pero la solución propuesta para este lhunath por utilizar mkdir tiene el defecto de que un script muerto puede dejar atrás el directorio, evitando así que otros casos se ejecute.


Otra opción es usar la opción noclobber de shell ejecutando el 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 atómicamente crea el archivo o falla si el archivo ya existe.

De acuerdo con un comentario sobre BashFAQ 045 , esto puede fallar en 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 bandera O_TRUNC , pero obviamente es redundante:
o bien estás creando un archivo vacío, o no estás haciendo nada.

Cómo lo hace depende de cómo quiere que se manejen las salidas no limpias.

Eliminar en la salida limpia

Las nuevas ejecuciones fallan hasta que se resuelva 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

Las nuevas ejecuciones tienen éxito siempre que el script no se esté ejecutando.

trap ''rm "$lockfile"'' EXIT


Para los scripts de shell, tiendo a ir con el mkdir sobre el 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 bloqueos aún se quedarán atrás.

Para una limpieza adecuada de la cerradura, realmente debe establecer sus trampas a algo así como este código psuedo (levantado, simplificado y no probado, pero a partir de scripts utilizados 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 siempre se producirá la función __sig_exit (salvo un SIGKILL) que limpiará los bloqueos.

Nota: mis valores de salida no son valores bajos. ¿Por qué? Varios sistemas de procesamiento por lotes tienen o tienen expectativas de los números del 0 al 31. Al establecerlos en otra cosa, puedo hacer que mis scripts y secuencias de lotes reaccionen de acuerdo con el trabajo por lotes o la secuencia de comandos anteriores.


Aquí hay una implementación que usa un archivo de bloqueo y hace eco de un PID en él. Esto sirve como protección si el proceso se cancela 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 envía ninguna señal sino que simplemente verifica si existe un proceso con el PID dado. Además, la llamada a trap asegurará que el archivo de bloqueo se elimine incluso cuando se mate el proceso (excepto kill -9 ).


Utilizo un enfoque simple que maneja 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, solo verificar si hay un proceso válido con el pid almacenado no es suficiente, especialmente para scripts de larga ejecución.

Utilizo noclobber para asegurarme de que solo una secuencia de comandos pueda abrir y escribir en el archivo de bloqueo a la vez. Además, almaceno suficiente información para identificar de manera única un proceso en el archivo de bloqueo. Defino el conjunto de datos para identificar de manera única un proceso para que sea pid, ppid, lstart.

Cuando se inicia una nueva secuencia de comandos, si no puede crear el archivo de bloqueo, entonces verifica que el proceso que creó el archivo de bloqueo todavía se encuentre. De lo contrario, suponemos que el proceso original murió de una manera desafortunada y dejó un archivo de bloqueo obsoleto. El nuevo script toma posesión del archivo de bloqueo, y todo está bien en el mundo, nuevamente.

Debería trabajar 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


Basta con añadir [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :al comienzo de su script. Es un código repetitivo del hombre rebaño. Para darse cuenta de cómo funciona i escribió un script y ejecutarlo simultáneamente desde dos consolas:

#!/bin/bash if [ "${FLOCKER}" != "$0" ]; then echo "FLOCKER=$FLOCKER /$0=$0 ($$)" exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || : else echo "FLOCKER equals /$0 = $FLOCKER ($$)" fi sleep 10 echo "Process $$ finished"

No me he dado cuenta completamente cómo funciona, pero parece que funciona sola vez usando como un fichero de bloqueo. FLOCKERajustado a "$0"sólo para establecer un valor notnull razonable. || :no hacer nada si algo ha ido mal.

Parece que no funciona en Debian 7, pero parece funcionar de nuevo con 2,25 experimental paquete util-linux. Se escribe "rebaño: ... archivo de texto ocupado". Podría ser anulado mediante la desactivación de permiso de escritura en la secuencia de comandos.


Aquí hay un enfoque que combina el bloqueo de directorio atómico con una verificación de bloqueo obsoleto a través de PID y reiniciar si está obsoleto. Además, esto no se basa en ningún bashisms.

#!/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 el rebaño del hombre, pero necesita algunas mejoras, porque debemos gestionar 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 procesos que utilicé en el pasado. Pero esto es más complicado que el método anterior. Debería enumerar los procesos por ps, filtrar por su nombre, grep de filtro adicional -v grep para eliminar el parásito nad, finalmente contarlo por grep -c. y compara con el número. Es complicado e incierto


Use flock(1) para hacer un bloqueo de ámbito exclusivo a en el descriptor de archivo. De esta forma, puedes incluso sincronizar diferentes partes del guion.

#!/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.


Todos los enfoques que prueban la existencia de "archivos de bloqueo" son defectuosos.

¿Por qué? Porque no hay forma de verificar si un archivo existe y crearlo en una sola acción atómica. Debido a esto; hay una condición de carrera que hará que tus intentos de exclusión mutua se rompan.

En cambio, necesitas usar mkdir . mkdir crea un directorio si todavía 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, consulte 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 fuser:

# 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 }

Puedes usarlo en un script como ese:

mutex /var/run/myscript.lock || { echo "Already running." >&2; exit 1; }

Si no le importa la portabilidad (estas soluciones deberían funcionar en prácticamente cualquier caja de UNIX), el fusor de Linux (1) ofrece algunas opciones adicionales y también hay rebajas (1) .


Los semafóricas usos de servicios públicos flock(como se discutió anteriormente, por ejemplo por presto8) para implementar un semáforo de conteo . Se permite cualquier número específico de procesos concurrentes que desee. La usamos para limitar el nivel de concurrencia de diversos procesos de trabajo de cola.

Es como SEM , pero mucho menor peso. (La revelación completa: La escribí después de encontrar el SEM era demasiado pesada para nuestras necesidades y no había una utilidad simple conteo de semáforos disponibles.)


Muy rápido y realmente sucio? Este one-liner 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. :)


Si las limitaciones de Flock, que ya se han descrito en otro lugar 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


Puede usar GNU Parallel para esto, ya que funciona como un mutex cuando se llama como sem . Entonces, en términos concretos, puedes usar:

sem --id SCRIPTSINGLETON yourScript

Si también quieres un tiempo de espera, utiliza:

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 ) else se establece de manera predeterminada en 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 Perl.


Aquí está una más elegante, a prueba de fallos, rápida y sucia método, que combina las respuestas proporcionadas anteriormente.

Uso

  1. incluir sh_lock_functions.sh
  2. init usando sh_lock_init
  3. bloquear el uso de sh_acquire_lock
  4. comprobar bloqueo con sh_check_lock
  5. desbloquear usando sh_remove_lock

Archivo de comandos

sh_lock_functions.sh

#!/bin/bash function sh_lock_init { sh_lock_scriptName=$(basename $0) sh_lock_dir="/tmp/${sh_lock_scriptName}.lock" #lock directory sh_lock_file="${sh_lock_dir}/lockPid.txt" #lock file } function sh_acquire_lock { if mkdir $sh_lock_dir 2>/dev/null; then #check for lock echo "$sh_lock_scriptName lock acquired successfully.">&2 touch $sh_lock_file echo $$ > $sh_lock_file # set current pid in lockFile return 0 else touch $sh_lock_file read sh_lock_lastPID < $sh_lock_file if [ ! -z "$sh_lock_lastPID" -a -d /proc/$sh_lock_lastPID ]; then # if lastPID is not null and a process with that pid exists echo "$sh_lock_scriptName is already running.">&2 return 1 else echo "$sh_lock_scriptName stopped during execution, reacquiring lock.">&2 echo $$ > $sh_lock_file # set current pid in lockFile return 2 fi fi return 0 } function sh_check_lock { [[ ! -f $sh_lock_file ]] && echo "$sh_lock_scriptName lock file removed.">&2 && return 1 read sh_lock_lastPID < $sh_lock_file [[ $sh_lock_lastPID -ne $$ ]] && echo "$sh_lock_scriptName lock file pid has changed.">&2 && return 2 echo "$sh_lock_scriptName lock still in place.">&2 return 0 } function sh_remove_lock { rm -r $sh_lock_dir }

ejemplo de uso

sh_lock_usage_example.sh

#!/bin/bash . /path/to/sh_lock_functions.sh # load sh lock functions sh_lock_init || exit $? sh_acquire_lock lockStatus=$? [[ $lockStatus -eq 1 ]] && exit $lockStatus [[ $lockStatus -eq 2 ]] && echo "lock is set, do some resume from crash procedures"; #monitoring example cnt=0 while sh_check_lock # loop while lock is in place do echo "$sh_scriptName running (pid $$)" sleep 1 let cnt++ [[ $cnt -gt 5 ]] && break done #remove lock when process finished sh_remove_lock || exit $? exit 0

Caracteristicas

  • Utiliza una combinación de archivo, directorio y identificador de proceso para bloquear para asegurarse de que el proceso no se está ejecutando
  • Puede detectar si el script se detuvo antes de la retirada de bloqueo (por ejemplo. Matar el proceso, apagado, error, etc.)
  • Puede comprobar el archivo de bloqueo, y lo utilizan para desencadenar un proceso de apagado cuando la cerradura está faltante
  • Detallado, da salida a los mensajes de error de depuración más fácil

¿Por qué no usamos algo parecido

pgrep -f $cmd || $cmd


if [ 1 -ne $(/bin/fuser "$0" 2>/dev/null | wc -w) ]; then exit 1 fi


Un ejemplo con rebaño (1), pero sin subcapa. flock () ed archivo / tmp / foo no se elimina, pero eso no importa como se pone flock () y ONU-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


Las respuestas publicadas existentes o bien se basan en la utilidad CLI flocko no asegurar correctamente el archivo de bloqueo. La utilidad rebaño no está disponible en todos los sistemas no Linux (es decir, FreeBSD), y no funciona correctamente en NFS.

En mis primeros días de la administración de sistemas y desarrollo de sistemas, me dijeron que un método seguro y relativamente portátil de crear un archivo de bloqueo era crear un archivo temporal utilizando mkemp(3)o mkemp(1), escribir la identificación de información para el archivo temporal (es decir, PID), que acceder a la dura el archivo temporal para el archivo de bloqueo. Si el enlace se ha realizado correctamente, entonces usted ha obtenido con éxito la cerradura.

Cuando el uso de bloqueos en secuencias de comandos shell, normalmente de realizar un obtain_lock()función en un perfil compartido y luego fuente que a partir 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 utilizar 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_upen cualquier punto de salida en la secuencia de comandos.

He utilizado el anterior, tanto en entornos Linux y FreeBSD.