¿Cuál es la mejor manera de garantizar que solo se esté ejecutando una instancia de un script Bash?[duplicar]

StackOverflow https://stackoverflow.com/questions/1715137

Pregunta

¿Cuál es la mejor y más sencilla manera de garantizar que solo se esté ejecutando una instancia de un script determinado, suponiendo que sea Bash en Linux?

En este momento estoy haciendo:

ps -C script.name.sh > /dev/null 2>&1 || ./script.name.sh

pero tiene varios problemas:

  1. pone el cheque fuera del script
  2. no me permite ejecutar el mismo script desde cuentas separadas, lo que a veces me gustaría.
  3. -C comprueba sólo los primeros 14 caracteres del nombre del proceso

Por supuesto, puedo escribir mi propio manejo de archivos pid, pero siento que debería haber una manera sencilla de hacerlo.

¿Fue útil?

Solución

Si el guión es el mismo en todos los usuarios, puede utilizar un enfoque lockfile. Si adquiere el bloqueo, proceda demás mostrar un mensaje y salir.

A modo de ejemplo:

[Terminal #1] $ lockfile -r 0 /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] lockfile: Sorry, giving up on "/tmp/the.lock"

[Terminal #1] $ rm -f /tmp/the.lock
[Terminal #1] $ 

[Terminal #2] $ lockfile -r 0 /tmp/the.lock
[Terminal #2] $ 

Después de /tmp/the.lock ha sido adquirido su guión será el único que tiene acceso a la ejecución. Cuando haya terminado, simplemente quitar el bloqueo. En esta forma de guión podría ser:

#!/bin/bash

lockfile -r 0 /tmp/the.lock || exit 1

# Do stuff here

rm -f /tmp/the.lock

Otros consejos

bloqueo consultivo ha sido utilizado para las edades y puede ser utilizado en scripts bash. Yo prefiero flock sencilla (de util-linux[-ng]) sobre lockfile (de procmail). Y siempre recordar acerca de una trampa en la salida (o EXIT 0 nombre-señal ==, atrapando a señales específicas es superflua) en estos scripts.

En 2009 publiqué mi guión repetitivo con llave (originalmente disponibles en mi página wiki, hoy en día disponibles como GIST ). La transformación de que en una sola instancia por usuario es trivial. Su utilización, usted también puede escribir fácilmente guiones para otros escenarios que requieren algún bloqueo o sincronización.

Aquí está el texto modelo mencionado para su conveniencia.

#!/bin/bash
# SPDX-License-Identifier: MIT

## Copyright (C) 2009 Przemyslaw Pawelczyk <przemoc@gmail.com>
##
## This script is licensed under the terms of the MIT license.
## https://opensource.org/licenses/MIT
#
# Lockable script boilerplate

### HEADER ###

LOCKFILE="/var/lock/`basename $0`"
LOCKFD=99

# PRIVATE
_lock()             { flock -$1 $LOCKFD; }
_no_more_locking()  { _lock u; _lock xn && rm -f $LOCKFILE; }
_prepare_locking()  { eval "exec $LOCKFD>\"$LOCKFILE\""; trap _no_more_locking EXIT; }

# ON START
_prepare_locking

# PUBLIC
exlock_now()        { _lock xn; }  # obtain an exclusive lock immediately or fail
exlock()            { _lock x; }   # obtain an exclusive lock
shlock()            { _lock s; }   # obtain a shared lock
unlock()            { _lock u; }   # drop a lock

### BEGIN OF SCRIPT ###

# Simplest example is avoiding running multiple instances of script.
exlock_now || exit 1

# Remember! Lock file is removed when one of the scripts exits and it is
#           the only script holding the lock or lock is not acquired at all.

Creo flock es probablemente el más fácil (y más memorable) variante. Yo lo uso en una tarea programada para auto-codificación dvds y cds

# try to run a command, but fail immediately if it's already running
flock -n /var/lock/myjob.lock   my_bash_command

Uso -w tiempos de espera o dejar de lado las opciones que esperar hasta que se libere el bloqueo. Por último, la página del manual muestra un buen ejemplo para varios comandos:

   (
     flock -n 9 || exit 1
     # ... commands executed under lock ...
   ) 9>/var/lock/mylockfile

Utilice la opción set -o noclobber e intentar sobrescribir un archivo común.

A ejemplo corto

if ! (set -o noclobber ; echo > /tmp/global.lock) ; then
    exit 1  # the global.lock already exists
fi

# ... remainder of script ...

Un ejemplo más largo

En este ejemplo se va a esperar a que el archivo global.lock pero el tiempo de espera después de mucho tiempo.

 function lockfile_waithold()
 {
    declare -ir time_beg=$(date '+%s')
    declare -ir time_max=7140  # 7140 s = 1 hour 59 min.

    # poll for lock file up to ${time_max}s
    # put debugging info in lock file in case of issues ...
    while ! \
       (set -o noclobber ; \
        echo -e "DATE:$(date)\nUSER:$(whoami)\nPID:$$" > /tmp/global.lock \ 
       ) 2>/dev/null
    do
        if [ $(($(date '+%s') - ${time_beg})) -gt ${time_max} ] ; then
            echo "Error: waited too long for lock file /tmp/global.lock" 1>&2
            return 1
        fi
        sleep 1
    done

    return 0
 }

 function lockfile_release()
 {
    rm -f /tmp/global.lock
 }

 if ! lockfile_waithold ; then
      exit 1
 fi
 trap lockfile_release EXIT

 # ... remainder of script ...


(Esto es similar a este post por @Barry Kelly que se notó después.)

No estoy seguro de que hay alguna solución robusta de una línea, por lo que podría llegar a rodar su propia cuenta.

ficheros de bloqueo son imperfectos, pero en menor medida que el uso de 'ps | grep | grep -v' tuberías.

Una vez dicho esto, es posible considerar mantener el control de procesos separarse de su guión - tener un script de inicio. O, al menos, el factor que a las funciones realizadas en un archivo separado, lo que es posible en la secuencia de comandos que llama tener:

. my_script_control.ksh

# Function exits if cannot start due to lockfile or prior running instance.
my_start_me_up lockfile_name;
trap "rm -f $lockfile_name; exit" 0 2 3 15

en cada secuencia de comandos que necesita la lógica de control. El trampa asegura que el fichero de bloqueo se elimina al salir del llamador, por lo que no tiene que codificar esto en cada punto de salida en el guión.

El uso de una secuencia de comandos de control independiente significa que se puede comprobar la cordura para casos extremos: eliminar archivos de registro rancios, verificar que el fichero de bloqueo está asociado correctamente con una instancia actualmente en ejecución del script, dan una opción para matar el proceso en ejecución, y así sucesivamente. También significa que usted tiene una mejor oportunidad de utilizar grep en la salida ps éxito. Un ps-grep se puede utilizar para verificar que un fichero de bloqueo tiene un proceso en ejecución asociado a él. Tal vez usted podría nombrar sus ficheros de bloqueo en alguna manera de incluir información sobre el proceso: usuario, PID, etc., que puede ser utilizado por una invocación de la escritura más tarde para decidir si el proceso que creó el fichero de bloqueo todavía está alrededor.

ejemplo primero prueba

[[ $(lsof -t $0| wc -l) > 1 ]] && echo "At least one of $0 is running"

ejemplo segunda prueba

currsh=$0
currpid=$$
runpid=$(lsof -t $currsh| paste -s -d " ")
if [[ $runpid == $currpid ]]
then
  sleep 11111111111111111
else
  echo -e "\nPID($runpid)($currpid) ::: At least one of \"$currsh\" is running !!!\n"
  false
  exit 1
fi

explicación

"lsof -t" para listar todos los PIDs de los guiones actuales de funcionamiento llamado "$ 0".

Comando "lsof" va a hacer dos ventajas.

  1. No haga caso de los PID que está editado en español por editor como vim, porque vim editar su archivo de asignación tales como ".file.swp".
  2. No haga caso de los PID bifurcadas mediante secuencias de comandos shell de ejecución actual, que la mayoría "grep" comando derivado no puede lograrlo. Utilizar el comando "pstree -pH pidnum" para ver detalles sobre el estado actual proceso que se bifurcan.

He encontrado esto en las dependencias del paquete procmail:

apt install liblockfile-bin

Para ejecutar: dotlockfile -l file.lock

Se creará file.lock.

Para desbloquear: dotlockfile -u file.lock

Utilice esta lista para este archivos de paquetes / comando: dpkg-query -L liblockfile-bin

distribuciones Ubuntu / Debian tienen la herramienta de start-stop-daemon que es para el mismo propósito que usted describe. Ver también /etc/init.d/skeleton para ver cómo se utiliza por escrito de arranque / parada guiones.

- Noah

También recomiendo mirar chpst (parte de runit):

chpst -L /tmp/your-lockfile.loc ./script.name.sh

Una solución última línea:

[ "$(pgrep -fn $0)" -ne "$(pgrep -fo $0)" ] && echo "At least 2 copies of $0 are running"

Tuve el mismo problema y se me ocurrió una plantilla que utiliza lockfile, un archivo pid que contiene el número de identificación del proceso y un kill -0 $(cat $pid_file) márquelo para que los scripts abortados no detengan la siguiente ejecución.Esto crea una carpeta foobar-$USERID en /tmp donde se encuentran el archivo de bloqueo y el archivo pid.

Aún puedes llamar el script y hacer otras cosas, siempre y cuando mantengas esas acciones en alertRunningPS.

#!/bin/bash

user_id_num=$(id -u)
pid_file="/tmp/foobar-$user_id_num/foobar-$user_id_num.pid"
lock_file="/tmp/foobar-$user_id_num/running.lock"
ps_id=$$

function alertRunningPS () {
    local PID=$(cat "$pid_file" 2> /dev/null)
    echo "Lockfile present. ps id file: $PID"
    echo "Checking if process is actually running or something left over from crash..."
    if kill -0 $PID 2> /dev/null; then
        echo "Already running, exiting"
        exit 1
    else
        echo "Not running, removing lock and continuing"
        rm -f "$lock_file"
        lockfile -r 0 "$lock_file"
    fi
}

echo "Hello, checking some stuff before locking stuff"

# Lock further operations to one process
mkdir -p /tmp/foobar-$user_id_num
lockfile -r 0 "$lock_file" || alertRunningPS

# Do stuff here
echo -n $ps_id > "$pid_file"
echo "Running stuff in ONE ps"

sleep 30s

rm -f "$lock_file"
rm -f "$pid_file"
exit 0

He encontrado una manera bastante simple de manejar "una copia de la escritura por sistema". Que no me permite ejecutar múltiples copias de la secuencia de comandos de muchas cuentas, aunque (en Linux estándar que es).

Solución:

A principios del guión, me di:

pidof -s -o '%PPID' -x $( basename $0 ) > /dev/null 2>&1 && exit

pidof funciona muy bien en una forma que:

  • que no tiene límite en el nombre del programa como ps -C ...
  • que no requiere que haga grep -v grep (o algo similar)

Y que no se basa en ficheros de bloqueo, que para mí es una gran victoria, porque la retransmisión en ellos significa que hay que añadir la manipulación de ficheros de bloqueo rancios - lo cual no es muy complicado, pero si se puede evitar - por qué no?

En cuanto a la comprobación con "una copia de la escritura por usuario ejecuta", escribí esto, pero no estoy demasiado contento con él:

(
    pidof -s -o '%PPID' -x $( basename $0 ) | tr ' ' '\n'
    ps xo pid= | tr -cd '[0-9\n]'
) | sort | uniq -d

y luego puedo comprobar su salida - si está vacío - no hay copias de la secuencia de comandos del mismo usuario

.

a partir de la secuencia de comandos:

ps -ef | grep $0 | grep $(whoami)

Aquí está nuestro bits estándar. Se puede recuperar a partir del guión de alguna manera morir sin limpiar que de fichero de bloqueo.

Se escribe el ID de proceso para el archivo de bloqueo si se ejecuta normalmente. Si encuentra un archivo de bloqueo cuando se empieza a correr, se leerá el ID de proceso del archivo de bloqueo y comprobar si existe ese proceso. Si no existe el proceso se eliminará el archivo de bloqueo rancio y continuar. Y sólo si existe el archivo de bloqueo y el proceso todavía se está ejecutando hará que la salida. Y escribe un mensaje cuando sale.

# lock to ensure we don't get two copies of the same job
script_name="myscript.sh"
lock="/var/run/${script_name}.pid"
if [[ -e "${lock}" ]]; then
    pid=$(cat ${lock})
    if [[ -e /proc/${pid} ]]; then
        echo "${script_name}: Process ${pid} is still running, exiting."
        exit 1
    else
        # Clean up previous lock file
        rm -f ${lock}
   fi
fi
trap "rm -f ${lock}; exit $?" INT TERM EXIT
# write $$ (PID) to the lock file
echo "$$" > ${lock}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top