programas manualmente job ejemplos ejecutar editar crear bash shell lockfile

bash - manualmente - Manera rápida y sucia de garantizar que solo se ejecute una instancia de un script de shell a la vez



ejecutar crontab manualmente (30)

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.

¿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?


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


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 ).


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.


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 }


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


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


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 flock(1) cómo usarlo en un script de shell.


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.


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 , 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


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 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.


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 (suposición, el proceso en 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.


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.


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


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:

Si desea cuidar los bloqueos obsoletos, el fuser(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 fuser(1) Linux fuser(1) ofrece algunas opciones adicionales y también hay flock(1) .


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.


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


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. :)


An example with flock(1) but without subshell. flock()ed file /tmp/foo is never removed, but that doesn''t matter as it gets flock() and 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


Here is a more elegant, fail-safe, quick & dirty method, combining the answers provided above.

Uso

  1. include sh_lock_functions.sh
  2. init using sh_lock_init
  3. lock using sh_acquire_lock
  4. check lock using sh_check_lock
  5. unlock using sh_remove_lock

Script File

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 }

Usage example

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

  • Uses a combination of file, directory and process id to lock to make sure that the process is not already running
  • You can detect if the script stopped before lock removal (eg. process kill, shutdown, error etc.)
  • You can check the lock file, and use it to trigger a process shutdown when the lock is missing
  • Verbose, outputs error messages for easier debug

I find that bmdhack''s solution is the most practical, at least for my use case. Using flock and lockfile rely on removing the lockfile using rm when the script terminates, which can''t always be guaranteed (eg, kill -9).

I would change one minor thing about bmdhack''s solution: It makes a point of removing the lock file, without stating that this is unnecessary for the safe working of this semaphore. His use of kill -0 ensures that an old lockfile for a dead process will simply be ignored/over-written.

My simplified solution is therefore to simply add the following to the top of your 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}

Of course, this script still has the flaw that processes that are likely to start at the same time have a race hazard, as the lock test and set operations are not a single atomic action. But the proposed solution for this by lhunath to use mkdir has the flaw that a killed script may leave behind the directory, thus preventing other instances from running.


I wanted to do away with lockfiles, lockdirs, special locking programs and even pidof since it isn''t found on all Linux installations. Also wanted to have the simplest code possible (or at least as few lines as possible). Simplest if statement, in one line:

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


Simply add [ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || : at the beginning of your script. It''s a boilerplate code from man flock. To realize how it works i wrote a script and run it simultaneously from two consoles:

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

I have not fully realized how it works, but it seems it runs itself again using itself as a lockfile. FLOCKER set to "$0" just to set some notnull reasonable value. || : to do nothing if something went wrong.

It seems to not work on Debian 7, but seems to work back again with experimental util-linux 2.25 package. It writes "flock: ... Text file busy". It could be overridden by disabling write permission on your script.


Take a look to FLOM (Free LOck Manager) http://sourceforge.net/projects/flom/ : you can synchronize commands and/or scripts using abstract resources that does not need lock files in a filesystem. You can synchronize commands running in different systems without a NAS (Network Attached Storage) like an NFS (Network File System) server.

Using the simplest use case, serializing "command1" and "command2" may be as easy as executing:

flom -- command1

y

flom -- command2

from two different shell scripts.


The semaphoric utility uses flock (as discussed above, eg by presto8) to implement a counting semaphore . It enables any specific number of concurrent processes you want. We use it to limit the level of concurrency of various queue worker processes.

It''s like sem but much lighter-weight. (Full disclosure: I wrote it after finding the sem was way too heavy for our needs and there wasn''t a simple counting semaphore utility available.)


The existing answers posted either rely on the CLI utility flock or do not properly secure the lock file. The flock utility is not available on all non-Linux systems (ie FreeBSD), and does not work properly on NFS.

In my early days of system administration and system development, I was told that a safe and relatively portable method of creating a lock file was to create a temp file using mkemp(3) or mkemp(1) , write identifying information to the temp file (ie PID), then hard link the temp file to the lock file. If the link was successful, then you have successfully obtained the lock.

When using locks in shell scripts, I typically place an obtain_lock() function in a shared profile and then source it from the scripts. Below is an example of my lock function:

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

The following is an example of how to use the lock function:

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

Remember to call clean_up at any exit points in your script.

I''ve used the above in both Linux and FreeBSD environments.


The flock path is the way to go. Think about what happens when the script suddenly dies. In the flock-case you just loose the flock, but that is not a problem. Also, note that an evil trick is to take a flock on the script itself .. but that of course lets you run full-steam-ahead into permission problems.


why dont we use something like

pgrep -f $cmd || $cmd


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