2009-09-17 8 views
13

Tengo un script bash que quiero ejecutar cada 5 minutos desde cron ... pero existe la posibilidad de que la ejecución previa del script aún no haya terminado ... en este caso, quiero que la nueva ejecución simplemente salga. No quiero confiar solo en un archivo de bloqueo en/tmp. Quiero asegurarme de que el proceso se esté ejecutando antes de cumplir con el archivo de bloqueo (o lo que sea) ...¿Cómo me aseguro de que mi script bash no se esté ejecutando?

Esto es lo que tengo robado de internet hasta ahora ... ¿cómo lo puedo arreglar un poco? o hay una forma completamente diferente que es mejor?

if [ -f /tmp/mylockFile ] ; then 
    echo 'Script is still running' 
else 
    echo 1 > /tmp/mylockFile 
    /* Do some stuff */ 
    rm -f /tmp/mylockFile 
fi 
+0

posible duplicado de [Forma rápida y sucia de garantizar que solo se ejecute una instancia de un script de shell a la vez] (http://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only- one-instance-of-a-shell-script-is-running-at) – user

Respuesta

21
# Use a lockfile containing the pid of the running process 
# If script crashes and leaves lockfile around, it will have a different pid so 
# will not prevent script running again. 
# 
lf=/tmp/pidLockFile 
# create empty lock file if none exists 
cat /dev/null >> $lf 
read lastPID < $lf 
# if lastPID is not null and a process with that pid exists , exit 
[ ! -z "$lastPID" -a -d /proc/$lastPID ] && exit 
echo not running 
# save my pid in the lock file 
echo $$ > $lf 
# sleep just to make testing easier 
sleep 5 

Hay al menos una condición de carrera en este script. No lo use para un sistema de soporte de vida, lol. Pero debería funcionar bien para su ejemplo, porque su entorno no inicia dos scripts simultáneamente. Hay muchas formas de usar más bloqueos atómicos, pero generalmente dependen de tener una cosa en particular instalada opcionalmente, o de trabajar de manera diferente en NFS, etc. ...

+1

¿Por qué 'cat/dev/null >> $ lf' en lugar de' touch $ lf'? ¿Por qué 'dormir' cuando no hay bucle? –

+0

'touch (1)' es bueno también. El sueño es solo para demostración, para que mi fragmento de guión pueda probarse por sí mismo. – DigitalRoss

+1

(Es decir, el pid se desvanecerá tan pronto como termine el script) – DigitalRoss

4

Si desea comprobar la existencia del proceso, con tan sólo mirar a la salida del

ps aux | grep your_script_name

Si está ahí, no está muerto ...

Como se señaló en el los comentarios y otras respuestas, utilizando el PID almacenado en el archivo de bloqueo es mucho más seguro y es el enfoque estándar que adoptan la mayoría de las aplicaciones. Simplemente hago esto porque es conveniente y casi nunca veo los casos de esquina (por ejemplo, editando el archivo cuando se ejecuta el cron) en la práctica.

+1

Además, puede almacenar el PID del proceso BASH actual en ese archivo de bloqueo. BASH proporciona una variable '$$' (menos las comillas) que da ese número. Vea aquí: http://tldp.org/LDP/abs/html/internalvariables.html para algunas de esas variables – Buggabill

+0

-1 ¿Qué pasa si "emacs your_script_name" es uno de los procesos en ejecución? – mob

+0

Ambos tienen toda la razón. El PID almacenado en el archivo sería una forma mucho mejor de hacerlo. Simplemente tiendo a hacerlo de esta manera porque soy flojo, mis guiones no son de misión crítica, y generalmente funciona (es bastante raro que en realidad estuviera editando el guión cuando se ejecuta el cron). –

4

Almacene su pid en mylockFile. Cuando necesite verificar, busque ps para el proceso con el pid que lee del archivo. Si existe, tu script se está ejecutando.

0

siempre puede:

if ps -e -o cmd | grep scriptname > /dev/null; then 
    exit 
fi 

Pero me gusta el fichero de bloqueo a mí mismo, por lo que no haría esto sin el archivo de bloqueo también.

+1

Dependiendo de lo portátil que sea, es posible que deba ajustar sus opciones ps, y puede agregar -q a grep en lugar de/dev/null –

+2

No es prueba suficiente del compañero de trabajo: alguien podría estar ejecutándose " menos scriptname "y eso sería suficiente para evitar que el script se ejecute – mob

+0

mobrule: Ya, nunca pensé en eso, simplemente porque nunca lo haría de esta manera ;-) El archivo de bloqueo es mejor. –

9

Es posible que desee echarle un vistazo a la página del manual para el flock comando, si tiene la suerte de obtenerlo en su distribución.

 
NAME 
     flock - Manage locks from shell scripts 
SYNOPSIS 
     flock [-sxon] [-w timeout] lockfile [-c] command... 
+0

Esta solución evita la condición de carrera entre la comprobación y la obtención del bloqueo de forma más limpia, a diferencia de algunas otras soluciones sugeridas. Si tiene 'flock (1)' (que viene con util-linux en debian/Ubuntu, etc.) definitivamente úselo. Para el caso de uso de OP, necesita un tiempo de espera corto (por ejemplo, -w 1). Si el comando ya se está ejecutando, el rebaño abandonará después de 1 segundo y no ejecutará el comando; de lo contrario, obtendrá el bloqueo e iniciará el comando. – arielf

+0

En lugar de usar '-w 1' para agotar el tiempo de espera y salir después de 1 segundo, el OP podría usar' -n, --nb, --nonblock Fail (con un código de salida de 1) en lugar de esperar si el bloqueo no puede ser inmediatamente adquirido. Ver el ejemplo en mi respuesta, que tristemente se agregó después de que este tema estuvo activo. – mattst

1

En algunos casos, es posible que desee ser capaz de distinguir entre lo que se está ejecutando la secuencia de comandos y permitir una cierta concurrencia, pero no todos. En ese caso, puede usar bloqueos por usuario, por tty o específicos de cron.

Puede usar variables de entorno como $ USER o la salida de un programa como tty para crear el nombre de archivo. Para cron, puede establecer una variable en el archivo crontab y probarla en su secuencia de comandos.

6

Nunca utilice un archivo de bloqueo siempre utilizar un directorio de bloqueo . En su caso específico, no es tan importante porque el inicio del script está programado en intervalos de 5 minutos. Pero si alguna vez vuelves a usar este código para un servidor web cgi-script, estarás feliz.

if mkdir /tmp/my_lock_dir 2>/dev/null 
then 
    echo "running now the script" 
    sleep 10 
    rmdir /tmp/my_lock_dir 
fi 

Esto tiene un problema si usted tiene un bloqueo rancio, significa que el bloqueo está ahí, pero ningún proceso asociado. Tu cron nunca se ejecutará.

¿Por qué usar un directorio? Porque mkdir es una operación atómica. Solo un proceso a la vez puede crear un directorio, todos los demás procesos obtienen un error. Esto incluso funciona en sistemas de archivos compartidos y probablemente incluso entre diferentes tipos de sistema operativo.

+0

Los archivos de bloqueo no funcionan en NFS, que es común en el alojamiento compartido. A menudo usamos directorios de bloqueo por la misma razón. –

3

Si usa un archivo de bloqueo, debe asegurarse de que siempre se elimine el archivo de bloqueo. Usted puede hacer esto con 'trampa':

if (set -o noclobber; echo "locked" > "$lockfile") 2> /dev/null; then 
    trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT 
    echo "Locking succeeded" >&2 
    rm -f "$lockfile" 
else 
    echo "Lock failed - exit" >&2 
    exit 1 
fi 

La opción noclobber hace que la creación de fichero de bloqueo atómica, como el uso de un directorio.

0

puede utilizar éste:

pgrep -f "/bin/\w*sh .*scriptname" | grep -vq $$ && exit 
3

Como una sola línea y si usted no desea utilizar un fichero de bloqueo (por ejemplo, b/c/de una única sistema de archivos leer, etc.)

test "$(pidof -x $(basename $0))" != $$ && exit 

Comprueba que la lista completa de PID que lleva el nombre de su script es igual al PID actual. El "-x" también verifica el nombre de los scripts de shell.

Bash hace que sea aún más corto y más rápido:

[[ "$(pidof -x $(basename $0))" != $$ ]] && exit 
1

yo estaba tratando de resolver este problema hoy en día y se me ocurrió el siguiente:

COMMAND_LINE="$0 $*" 
JOBS=$(SUBSHELL_PID=$BASHPID; ps axo pid,command | grep "${COMMAND_LINE}" | grep -v $$ | g rep -v ${SUBSHELL_PID} | grep -v grep) 
if [[ -z "${JOBS}" ]] 
then 
    # not already running 
else 
    # already running 
fi 

Esto se basa en $BASHPID que contiene el PID dentro de una subshell ($$ en la subshell es el pid padre). Sin embargo, esto depende de Bash v4 y necesitaba ejecutar esto en OSX que tiene Bash v3.2.48. Yo en última instancia, se le ocurrió otra solución y es más limpio:

JOBS=$(sh -c "ps axo pid,command | grep \"${COMMAND_LINE}\" | grep -v grep | grep -v $$") 
0

Desde una solución zócalo todavía no se ha mencionado, es de destacar que las tomas se pueden utilizar como mutex eficaces. La creación de socket es una operación atómica, como mkdir es como Gunstick señaló, por lo que un socket es adecuado para usar como un bloqueo o mutex.

La secuencia de comandos de Tim Kay Perl 'Solo' es una secuencia de comandos muy pequeña y efectiva para asegurarse de que solo se puede ejecutar una copia de una secuencia de comandos al mismo tiempo. Fue diseñado específicamente para usarse con trabajos cron, aunque también funciona perfectamente para otras tareas y lo he usado para trabajos que no son de tipo croar de manera muy efectiva.

Solo tiene una ventaja sobre las otras técnicas mencionadas hasta el momento en que la verificación se realiza fuera del script del que solo desea ejecutar una copia. Si la secuencia de comandos ya se está ejecutando, una segunda instancia de esa secuencia de comandos nunca se iniciará. Esto es opuesto a aislar un bloque de código dentro de la secuencia de comandos que está protegido por un bloqueo. EDITAR: Si se usa flock en un trabajo cron, en lugar de desde dentro de un script, también puede usarlo para evitar que se inicie una segunda instancia del script - vea el ejemplo a continuación.

Aquí hay un ejemplo de cómo se puede utilizar con cron:

*/5 * * * * solo -port=3801 /path/to/script.sh args args args 

# "/path/to/script.sh args args args" is only called if no other instance of 
# "/path/to/script.sh" is running, or more accurately if the socket on port 3801 
# is not open. Distinct port numbers can be used for different programs so that 
# if script_1.sh is running it does not prevent script_2.sh from starting, I've 
# used the port range 3801 to 3810 without conflicts. For Linux non-root users 
# the valid port range is 1024 to 65535 (0 to 1023 are reserved for root). 

* * * * * solo -port=3802 /path/to/script_1.sh 
* * * * * solo -port=3803 /path/to/script_2.sh 

# Flock can also be used in cron jobs with a distinct lock path for different 
# programs, in the example below script_3.sh will only be started if the one 
# started a minute earlier has already finished. 

* * * * * flock -n /tmp/path.to.lock -c /path/to/script_3.sh 

Enlaces:

Esperanza esto ayuda.

0

Puede usar this.

Voy a copiar y pegar descaradamente la solución aquí, ya que es una respuesta para ambas preguntas (yo diría que en realidad es una mejor opción para esta pregunta).

Uso

  1. incluyen sh_lock_functions.sh
  2. init usando sh_lock_init
  3. de bloqueo con sh_acquire_lock
  4. bloqueo cheque utilizando sh_check_lock
  5. ONU bloqueo con sh_remove_lock

Archivo de Escritura

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 

Características

  • 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 proceso, apagado, error, etc.)
  • Puede comprobar el archivo de bloqueo, y lo utilizan para desencadenar un proceso de apagado cuando la cerradura no se encuentra
  • detallado, emite mensajes de error de depuración más fácil
Cuestiones relacionadas