Una soluzione che non richiede strumenti aggiuntivi sarebbe preferita.
ln -s my.pid .lock
rivendicherà il blocco (seguito da echo $$ > my.pid
) e in caso di errore può verificare se il PID archiviato .lock
è davvero un'istanza attiva dello script
Una soluzione che non richiede strumenti aggiuntivi sarebbe preferita.
ln -s my.pid .lock
rivendicherà il blocco (seguito da echo $$ > my.pid
) e in caso di errore può verificare se il PID archiviato .lock
è davvero un'istanza attiva dello script
Risposte:
Quasi come la risposta di nsg: usa una directory di blocco . La creazione di directory è atomica su Linux, Unix e * BSD e molti altri sistemi operativi.
if mkdir $LOCKDIR
then
# Do important, exclusive stuff
if rmdir $LOCKDIR
then
echo "Victory is mine"
else
echo "Could not remove lock dir" >&2
fi
else
# Handle error condition
...
fi
È possibile inserire il PID del blocco sh in un file nella directory di blocco per scopi di debug, ma non cadere nella trappola di pensare che è possibile controllare quel PID per vedere se il processo di blocco viene ancora eseguito. Molte condizioni di gara si trovano su quel percorso.
mkdir
non è atomica su NFS (il che non è il mio caso, ma immagino che si debba dire che, se fosse vero)
/tmp
cui di solito non è condiviso NFS, quindi dovrebbe andare bene.
rm -rf
per rimuovere la directory di blocco. rmdir
fallirà se qualcuno (non necessariamente tu) è riuscito ad aggiungere un file alla directory.
Per aggiungere alla risposta di Bruce Ediger e ispirato a questa risposta , dovresti anche aggiungere più intelligenza alla pulizia per evitare la fine dello script:
#Remove the lock directory
function cleanup {
if rmdir $LOCKDIR; then
echo "Finished"
else
echo "Failed to remove lock directory '$LOCKDIR'"
exit 1
fi
}
if mkdir $LOCKDIR; then
#Ensure that if we "grabbed a lock", we release it
#Works for SIGTERM and SIGINT(Ctrl-C)
trap "cleanup" EXIT
echo "Acquired lock, running"
# Processing starts here
else
echo "Could not create lock directory '$LOCKDIR'"
exit 1
fi
if ! mkdir "$LOCKDIR"; then handle failure to lock and exit; fi trap and do processing after if-statement
.
Questo potrebbe essere troppo semplicistico, per favore correggimi se sbaglio. Non è ps
abbastanza semplice ?
#!/bin/bash
me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;
# do stuff below this comment
grep -v $$
. esempi reali: vecchio - 14532, nuovo - 1453, vecchio - 28858, nuovo - 858.
grep -v $$
ingrep -v "^${$} "
grep -wv "^$$"
(vedi modifica).
Vorrei usare un file di blocco, come menzionato da Marco
#!/bin/bash
# Exit if /tmp/lock.file exists
[ -f /tmp/lock.file ] && exit
# Create lock file, sleep 1 sec and verify lock
echo $$ > /tmp/lock.file
sleep 1
[ "x$(cat /tmp/lock.file)" == "x"$$ ] || exit
# Do stuff
sleep 60
# Remove lock file
rm /tmp/lock.file
lsof $0
non è male, neanche?
$$
nel file di blocco. Quindi sleep
per un breve intervallo e rileggilo. Se il PID è ancora tuo, hai acquisito correttamente il blocco. Non necessita assolutamente di strumenti aggiuntivi.
Se vuoi assicurarti che sia in esecuzione solo un'istanza del tuo script, dai un'occhiata a:
Blocca il tuo script (contro la corsa parallela)
Altrimenti puoi controllare ps
o invocare lsof <full-path-of-your-script>
, dal momento che non li chiamerei strumenti aggiuntivi.
Supplemento :
in realtà ho pensato di farlo in questo modo:
for LINE in `lsof -c <your_script> -F p`; do
if [ $$ -gt ${LINE#?} ] ; then
echo "'$0' is already running" 1>&2
exit 1;
fi
done
questo garantisce che solo il processo con il minimo pid
continui a funzionare anche se si eseguono fork-and-exec diverse istanze <your_script>
contemporaneamente.
[[(lsof $0 | wc -l) > 2]] && exit
potrebbe effettivamente essere sufficiente, o è anche incline alle condizioni di gara?
Un altro modo per assicurarsi che venga eseguita una singola istanza di script bash:
#!/bin/bash
# Check if another instance of script is running
pidof -o %PPID -x $0 >/dev/null && echo "ERROR: Script $0 already running" && exit 1
...
pidof -o %PPID -x $0
ottiene il PID dello script esistente se è già in esecuzione o esce con il codice di errore 1 se nessun altro script è in esecuzione
Sebbene tu abbia chiesto una soluzione senza strumenti aggiuntivi, questo è il mio modo preferito usando flock
:
#!/bin/sh
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
echo "servus!"
sleep 10
Questo deriva dalla sezione degli esempi di man flock
, che spiega ulteriormente:
Questo è utile codice boilerplate per gli script di shell. Inseriscilo nella parte superiore dello script della shell che vuoi bloccare e si bloccherà automaticamente alla prima esecuzione. Se env var $ FLOCKER non è impostato sullo script della shell in esecuzione, eseguire flock e acquisire un blocco esclusivo non bloccante (utilizzando lo script stesso come file di blocco) prima di rieseguire se stesso con gli argomenti corretti. Inoltre imposta FLOCKER env var sul valore corretto in modo che non venga eseguito di nuovo.
Punti da considerare:
flock
, lo script di esempio termina con un errore se non può essere trovatoVedi anche /programming/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at .
Questa è una versione modificata della risposta di Anselmo . L'idea è di creare un descrittore di file di sola lettura utilizzando lo script bash stesso e utilizzarlo flock
per gestire il blocco.
SCRIPT=`realpath $0` # get absolute path to the script itself
exec 6< "$SCRIPT" # open bash script using file descriptor 6
flock -n 6 || { echo "ERROR: script is already running" && exit 1; } # lock file descriptor 6 OR show error message if script is already running
echo "Run your single instance code here"
La principale differenza rispetto a tutte le altre risposte è che questo codice non modifica il filesystem, utilizza un footprint molto basso e non necessita di alcuna pulizia poiché il descrittore di file viene chiuso non appena lo script termina indipendentemente dallo stato di uscita. Quindi non importa se lo script fallisce o ha esito positivo.
exec 6< "$SCRIPT"
.
Sto usando cksum per verificare che il mio script stia davvero eseguendo una singola istanza, anche se cambio il nome del file e il percorso del file .
Non sto usando il file trap & lock, perché se il mio server improvvisamente si spegne, devo rimuovere manualmente il file di blocco dopo che il server è andato su.
Nota: #! / Bin / bash in prima riga è richiesto per grep ps
#!/bin/bash
checkinstance(){
nprog=0
mysum=$(cksum $0|awk '{print $1}')
for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do
proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash)
if [[ $? -eq 0 ]];then
cmd=$(strings /proc/$i/cmdline|grep -v bash)
if [[ $? -eq 0 ]];then
fsum=$(cksum /proc/$i/cwd/$cmd|awk '{print $1}')
if [[ $mysum -eq $fsum ]];then
nprog=$(($nprog+1))
fi
fi
fi
done
if [[ $nprog -gt 1 ]];then
echo $0 is already running.
exit
fi
}
checkinstance
#--- run your script bellow
echo pass
while true;do sleep 1000;done
Oppure puoi cksum hardcoded all'interno del tuo script, quindi non ti preoccupare di nuovo se vuoi cambiare nome file, percorso o contenuto del tuo script .
#!/bin/bash
mysum=1174212411
checkinstance(){
nprog=0
for i in `ps -ef |grep /bin/bash|awk '{print $2}'`;do
proc=$(ls -lha /proc/$i/exe 2> /dev/null|grep bash)
if [[ $? -eq 0 ]];then
cmd=$(strings /proc/$i/cmdline|grep -v bash)
if [[ $? -eq 0 ]];then
fsum=$(grep mysum /proc/$i/cwd/$cmd|head -1|awk -F= '{print $2}')
if [[ $mysum -eq $fsum ]];then
nprog=$(($nprog+1))
fi
fi
fi
done
if [[ $nprog -gt 1 ]];then
echo $0 is already running.
exit
fi
}
checkinstance
#--- run your script bellow
echo pass
while true;do sleep 1000;done
mysum
e fsum
, quando non parli più di un checksum.
Puoi usare questo: https://github.com/sayanarijit/pidlock
sudo pip install -U pidlock
pidlock -n sleepy_script -c 'sleep 10'
Il mio codice per te
#!/bin/bash
script_file="$(/bin/readlink -f $0)"
lock_file=${script_file////_}
function executing {
echo "'${script_file}' already executing"
exit 1
}
(
flock -n 9 || executing
sleep 10
) 9> /var/lock/${lock_file}
Basato su man flock
, migliorando solo:
executing
Dove ho messo qui il sleep 10
, puoi mettere tutto lo script principale.