2009-09-17 8 views
13

Ho uno script di bash che voglio eseguire ogni 5 minuti da cron ... ma c'è una possibilità che la precedente esecuzione dello script non sia ancora finita ... in questo caso, voglio che la nuova esecuzione sia appena terminata. Non voglio fare affidamento solo su un file di blocco in/tmp. Voglio assicurarmi che il processo sia effettivamente in esecuzione prima di onorare il file di blocco (o qualsiasi altra cosa) ...Come faccio a essere sicuro che il mio script di bash non sia già in esecuzione?

Ecco cosa ho rubato da internet fino ad ora ... come faccio a renderlo un po 'più intelligente? o c'è un modo completamente diverso che è meglio?

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

possibile duplicato di [Modo rapido e sporco per garantire che sia in esecuzione una sola istanza di uno script di shell alla volta] (http://stackoverflow.com/questions/185451/quick-and-dirty-way-to-ensure-only- one-instance-of-a-shell-script-is-running-at) – user

risposta

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 

C'è almeno una condizione di competizione in questo script. Non usarlo per un sistema di supporto vitale, lol. Ma dovrebbe funzionare bene per il tuo esempio, perché il tuo ambiente non avvia due script contemporaneamente. Esistono molti modi per utilizzare più blocchi atomici, ma generalmente dipendono dall'avere installato una particolare opzione, o funzionare in modo diverso su NFS, ecc ...

+1

Perché 'cat/dev/null >> $ lf' invece di' touch $ lf'? Perché "dormire" quando non c'è il ciclo? –

+0

'touch (1)' è buono anche. Il sonno è solo per dimostrazione, per rendere il mio frammento di script testabile da solo. – DigitalRoss

+1

(Ovvero, il pid svanirà non appena termina lo script) – DigitalRoss

4

Se si desidera controllare l'esistenza del processo, basta guardare l'uscita di

ps aux | grep your_script_name

Se è lì, non è morto ...

Come sottolineato nella commenti e altre risposte, utilizzando il PID memorizzato nel file di lock è molto più sicuro ed è l'approccio standard adottato dalla maggior parte delle app. Lo faccio solo perché è comodo e non vedo quasi mai i casi d'angolo (ad esempio la modifica del file quando lo cron esegue) in pratica.

+1

Inoltre, è possibile memorizzare il PID del processo BASH corrente in tale file di blocco. BASH fornisce una variabile '$$' (meno le virgolette) che dà quel numero. Vedi qui: http://tldp.org/LDP/abs/html/internalvariables.html per alcune di queste variabili – Buggabill

+0

-1 Cosa succede se "emacs your_script_name" è uno dei processi in esecuzione? – mob

+0

Hai assolutamente ragione. Il PID memorizzato nel file sarebbe un modo molto migliore di andare. Tendo solo a farlo in questo modo perché sono pigro, i miei script non sono mission-critical, e di solito funziona (è piuttosto raro che io stia effettivamente modificando lo script quando esegue il cron). –

4

Conservare il pid in mylockFile. Quando hai bisogno di controllare, cerca ps per il processo con il pid che leggi dal file. Se esiste, lo script è in esecuzione.

0

Si può sempre e solo:

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

Ma mi piace il file di lock me stesso, in modo da non fare questo senza il file di blocco pure.

+1

A seconda di come questo portatile deve essere, potrebbe essere necessario modificare le opzioni di ps, e potresti essere in grado di aggiungere -q a grep invece di/dev/null –

+2

Non abbastanza di prova da collaboratore: qualcuno potrebbe essere in esecuzione " less scriptname "e sarebbe sufficiente per impedire allo script di eseguire mobrule – mob

+0

: Ya, non ci ho mai pensato, solo perché non lo farei mai in questo modo ;-) Il lockfile è migliore. –

9

Si potrebbe voler dare un'occhiata alla pagina man per Comando flock, se sei abbastanza fortunato da averlo sulla tua distribuzione.

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

Questa soluzione evita la condizione di competizione tra il controllo e l'ottenimento del lucchetto in modo più pulito, a differenza di altre soluzioni suggerite. Se hai 'flock (1)' (che viene fornito con util-linux in debian/Ubuntu etc) sicuramente lo usa. Per il caso d'uso dell'OP, è necessario un breve timeout (ad es. -w 1). Se il comando è già in esecuzione, il gruppo si arrenderà dopo 1 secondo e non eseguirà il comando, altrimenti otterrà il blocco e avvierà il comando. – arielf

+0

Invece di usare '-w 1' per timeout ed uscire dopo 1 secondo, l'OP potrebbe usare' -n, --nb, --nonblock Fail (con un codice di uscita di 1) invece di aspettare se il blocco non può essere immediatamente acquisito. Vedere l'esempio nella mia risposta che è stato aggiunto tristemente dopo che questo argomento era attivo. – mattst

1

In alcuni casi, si potrebbe desiderare di essere in grado di distinguere tra chi è in esecuzione lo script e consentire una certa concorrenza, ma non tutti. In tal caso, puoi utilizzare blocchi per utente, per tty o cron specifici.

È possibile utilizzare variabili di ambiente come $ USER o l'output di un programma come tty per creare il nome file. Per cron, è possibile impostare una variabile nel file crontab e verificarla nel proprio script.

6

Non utilizzare mai un lucchetto file utilizzare sempre un lucchetto directory. Nel tuo caso specifico, non è così importante perché l'inizio dello script è programmato a intervalli di 5 minuti. Ma se riusciate a riutilizzare questo codice per un cgi-script del server web, siete brindati.

Questo ha un problema se si dispone di un blocco stantio, significa che il blocco è lì, ma nessun processo associato. Il tuo cron non funzionerà mai.

Perché utilizzare una directory? Perché mkdir è un'operazione atomica. Solo un processo alla volta può creare una directory, tutti gli altri processi ottengono un errore. Funziona anche su filesystem condivisi e probabilmente anche tra diversi tipi di OS.

+0

I file di blocco non funzionano su NFS, che è comune nell'hosting condiviso. Spesso usiamo le directory di blocco per lo stesso motivo. –

3

Se si utilizza un file di blocco, è necessario assicurarsi che il file di blocco venga sempre rimosso. È possibile farlo con 'trappola':

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 

L'opzione noclobber rende la creazione di file di lock atomica, come l'utilizzo di una directory.

0

è possibile utilizzare questo:

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

come un one-liner e se non si desidera utilizzare un file di lock (per esempio, b/c/di una sola lettura del file system, ecc)

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

Controlla che l'elenco completo di PID che porta il nome del tuo script sia uguale al PID corrente. "-x" controlla anche il nome degli script della shell.

Bash rende ancora più breve e più veloce:

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

stavo cercando di risolvere questo problema oggi e sono arrivato fino a qui sotto:

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 

Questo si basa su $BASHPID che contiene il PID all'interno di una subshell ($$ nella subshell è il padre pid). Tuttavia, questo si basa su Bash v4 e ho avuto bisogno di eseguire questo su OSX che ha Bash v3.2.48. Sono infine si avvicinò con un'altra soluzione ed è pulito:

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

Poiché una soluzione presa non è ancora stato menzionato è opportuno sottolineare che le prese possono essere utilizzati come mutex efficaci. La creazione di socket è un'operazione atomica, come mkdir è come indicato da Gunstick, quindi una presa è adatta per essere utilizzata come lock o come mutex.

Lo script Perl di Tim Kay "Solo" è uno script molto piccolo ed efficace per assicurarsi che sia possibile eseguire solo una copia di uno script in qualsiasi momento. È stato progettato specificamente per l'uso con i lavori di cron, anche se funziona perfettamente anche per altri compiti e l'ho usato per i lavori non-crob in modo molto efficace.

Solo ha un vantaggio rispetto alle altre tecniche menzionate finora in quanto il controllo viene eseguito al di fuori dello script di cui si desidera eseguire solo una copia. Se lo script è già in esecuzione, una seconda istanza di quello script non verrà nemmeno avviata. Questo è al contrario di isolare un blocco di codice all'interno dello script che è protetto da un blocco. MODIFICA: se flock viene utilizzato in un processo cron piuttosto che all'interno di uno script, è anche possibile utilizzarlo per impedire l'avvio di una seconda istanza dello script, vedere l'esempio di seguito.

Ecco un esempio di come è possibile utilizzare 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 

vicini:

Speranza questo aiuta.

0

È possibile utilizzare this.

Mi limito ad incollare senza vergogna la soluzione qui, poiché è una risposta per entrambe le domande (direi che in realtà è una soluzione migliore per questa domanda).

Uso

  1. includono sh_lock_functions.sh
  2. init utilizza sh_lock_init
  3. serratura usando sh_acquire_lock
  4. blocco di controllo utilizzando sh_check_lock
  5. un Blocco utilizzando sh_remove_lock file

Script

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 
} 

esempio di utilizzo

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 

Caratteristiche

  • utilizza una combinazione di file, directory e il processo di identificazione per bloccare per assicurarsi che il processo non è già in esecuzione
  • è possibile rilevare se lo script interrotto prima della rimozione di blocco (ad es. processo di uccidere, l'arresto, errore, ecc)
  • È possibile controllare il file di blocco, e utilizzarlo per innescare un arresto del processo quando il blocco non è presente
  • Verbose, genera messaggi di errore per le operazioni di debug
Problemi correlati