Uma solução que não requer ferramentas adicionais seria preferida.
ln -s my.pid .lock
vai reivindicar o bloqueio (seguido echo $$ > my.pid
) e em caso de falha pode verificar se o PID armazenado em .lock
é realmente uma instância ativa do script
Uma solução que não requer ferramentas adicionais seria preferida.
ln -s my.pid .lock
vai reivindicar o bloqueio (seguido echo $$ > my.pid
) e em caso de falha pode verificar se o PID armazenado em .lock
é realmente uma instância ativa do script
Respostas:
Quase como a resposta do nsg: use um diretório de bloqueio . A criação de diretórios é atômica sob linux e unix e * BSD e muitos outros sistemas operacionais.
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
Você pode colocar o PID do bloqueio sh em um arquivo no diretório de bloqueio para fins de depuração, mas não caia na armadilha de pensar que pode verificar esse PID para ver se o processo de bloqueio ainda é executado. Muitas condições de corrida se encontram nesse caminho.
mkdir
não é atômica sobre NFS (que não é o caso para mim, mas eu acho que deve-se mencionar que, se for verdade)
/tmp
qual geralmente não é compartilhado pelo NFS, portanto tudo bem.
rm -rf
para remover o diretório de bloqueio. rmdir
falhará se alguém (não necessariamente você) conseguir adicionar um arquivo ao diretório.
Para adicionar à resposta de Bruce Ediger , e inspirado nessa resposta , você também deve adicionar mais inteligência à limpeza para se proteger contra o encerramento de scripts:
#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
,.
Isso pode ser muito simplista, corrija-me se estiver errado. Não é simples o ps
suficiente?
#!/bin/bash
me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;
# do stuff below this comment
grep -v $$
. exemplos reais: antigo - 14532, novo - 1453, antigo - 28858, novo - 858.
grep -v $$
paragrep -v "^${$} "
grep -wv "^$$"
(veja editar).
Eu usaria um arquivo de bloqueio, como mencionado por 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
não seja ruim, também?
$$
no arquivo de bloqueio. Depois, sleep
por um curto intervalo e leia-o novamente. Se o PID ainda for seu, você adquiriu o bloqueio com sucesso. Não precisa absolutamente de ferramentas adicionais.
Se você deseja garantir que apenas uma instância do seu script esteja em execução, consulte:
Bloqueie seu script (contra execução paralela)
Caso contrário, você pode verificar ps
ou invocar lsof <full-path-of-your-script>
, pois eu não os chamaria de ferramentas adicionais.
Suplemento :
na verdade, eu pensei em fazê-lo assim:
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
isso garante que apenas o processo com o menor pid
continue em execução, mesmo se você forçar e executar várias instâncias <your_script>
simultaneamente.
[[(lsof $0 | wc -l) > 2]] && exit
pode ser suficiente, ou isso também é propenso a condições de corrida?
Outra maneira de garantir que uma única instância do script bash seja executada:
#!/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
obtém o PID do script existente se ele já estiver em execução ou sai com o código de erro 1 se nenhum outro script estiver em execução
Embora você tenha solicitado uma solução sem ferramentas adicionais, esta é a minha maneira favorita de usar flock
:
#!/bin/sh
[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock -en "$0" "$0" "$@" || :
echo "servus!"
sleep 10
Isso vem da seção de exemplos man flock
, que explica ainda:
Esse é um código útil para scripts de shell. Coloque-o na parte superior do script de shell que você deseja bloquear e ele se bloqueará automaticamente na primeira execução. Se o env var $ FLOCKER não estiver definido como o shell script que está sendo executado, execute o flock e pegue um bloqueio exclusivo sem bloqueio (usando o próprio script como arquivo de bloqueio) antes de se executar novamente com os argumentos corretos. Ele também define o env var FLOCKER para o valor certo, para que não seja executado novamente.
Pontos a considerar:
flock
, o script de exemplo termina com um erro se não puder ser encontradoConsulte também /programming/185451/quick-and-dirty-way-to-ensure-only-one-instance-of-a-shell-script-is-running-at .
Esta é uma versão modificada da resposta de Anselmo . A idéia é criar um descritor de arquivo somente leitura usando o próprio script bash e use flock
para manipular o bloqueio.
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"
A principal diferença para todas as outras respostas é que esse código não modifica o sistema de arquivos, usa uma área muito baixa e não precisa de nenhuma limpeza, pois o descritor de arquivo é fechado assim que o script termina independentemente do estado de saída. Portanto, não importa se o script falhar ou for bem-sucedido.
exec 6< "$SCRIPT"
.
Estou usando o cksum para verificar se meu script está realmente executando uma instância única, mesmo que eu mude o nome do arquivo e o caminho do arquivo .
Não estou usando o arquivo de interceptação e bloqueio, porque se meu servidor estiver inoperante repentinamente, preciso remover manualmente o arquivo de bloqueio após a inicialização do servidor.
Nota: #! / Bin / bash na primeira linha é necessário para 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
Ou você pode codificar cksum no seu script para não se preocupar novamente se quiser alterar o nome do arquivo, o caminho ou o conteúdo do 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 não estiver mais falando sobre uma soma de verificação.
Você pode usar isso: https://github.com/sayanarijit/pidlock
sudo pip install -U pidlock
pidlock -n sleepy_script -c 'sleep 10'
Meu código para você
#!/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}
Com base em man flock
, melhorando apenas:
executing
Onde eu coloquei aqui o sleep 10
, você pode colocar todo o script principal.