Aqui está uma implementação que usa um arquivo de bloqueio e faz eco de um PID nele. Isso serve como uma proteção se o processo for morto antes de remover o pidfile :
LOCKFILE=/tmp/lock.txt
if[-e ${LOCKFILE}]&& kill -0`cat ${LOCKFILE}`;then
echo "already running"
exit
fi# make sure the lockfile is removed when we exit and then claim it
trap "rm -f ${LOCKFILE}; exit" INT TERM EXIT
echo $$ > ${LOCKFILE}# do stuff
sleep 1000
rm -f ${LOCKFILE}
O truque aqui é o kill -0que não entrega nenhum sinal, mas apenas verifica se existe um processo com o PID especificado. Além disso, a chamada para trapgarantirá que o arquivo de bloqueio seja removido mesmo quando seu processo for encerrado (exceto kill -9).
Como já mencionado em um comentário sobre outra resposta, isso tem uma falha fatal - se o outro script iniciar entre a verificação e o eco, você estará brindando.
Paul Tomblin
1
O truque do link simbólico é legal, mas se o proprietário do arquivo de bloqueio for morto -9'd ou o sistema travar, ainda haverá uma condição de corrida para ler o link simbólico, observe que o proprietário se foi e exclua-o. Estou aderindo à minha solução.
9338 bmdhacks
9
A verificação atômica e a criação estão disponíveis no shell usando flock (1) ou lockfile (1). Veja outras respostas.
dmckee --- ex-moderador gatinho
3
Veja minha resposta para uma maneira portátil de fazer uma verificação atômica e criar sem precisar confiar em utilitários como flock ou lockfile.
Lhunath 08/04/09
2
Isso não é atômico e, portanto, é inútil. Você precisa de um mecanismo atômico para testar e definir.
K Richard Pixley
214
Use flock(1)para tornar um bloqueio de escopo exclusivo um descritor de arquivo. Dessa forma, você pode até sincronizar diferentes partes do script.
#!/bin/bash(# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10200|| exit 1# Do stuff)200>/var/lock/.myscript.exclusivelock
Isso garante que o código entre (e )seja executado apenas por um processo por vez e que o processo não espere muito tempo por um bloqueio.
Advertência: este comando em particular faz parte util-linux. Se você executar um sistema operacional diferente do Linux, ele pode ou não estar disponível.
Qual é o 200? Diz "fd" no manul, mas não sei o que isso significa.
chovy
4
@chovy "file descriptor", um identificador inteiro que designa um arquivo aberto.
Alex B
6
Se alguém mais estiver se perguntando: a sintaxe ( command A ) command Bchama um subshell para command A. Documentado em tldp.org/LDP/abs/html/subshells.html . Eu ainda não estou certo sobre o momento da invocação do subshell e comando B.
Dr. Jan-Philip Gehrcke
1
Eu acho que o código dentro do sub-shell deve ser mais parecido: if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fipara que, se ocorrer o tempo limite (algum outro processo tiver o arquivo bloqueado), esse script não avance e modifique o arquivo. Provavelmente ... o contra-argumento é 'mas se demorou 10 segundos e o bloqueio ainda não está disponível, nunca estará disponível', provavelmente porque o processo que contém o bloqueio não está terminando (talvez esteja sendo executado sob um depurador?).
9788 Jonathan-Leffler1
1
O arquivo para o qual o redirecionado é apenas um dobrador de places para o bloqueio, não há dados significativos nele. O exité da parte dentro do (). Quando o subprocesso termina, o bloqueio é liberado automaticamente, porque não há processo mantendo-o.
clacke
158
Todas as abordagens que testam a existência de "arquivos de bloqueio" são falhas.
Por quê? Porque não há como verificar se existe um arquivo e criá-lo em uma única ação atômica. Por causa disso; há uma condição de corrida que vai fazer as suas tentativas de quebra de exclusão mútua.
Em vez disso, você precisa usar mkdir. mkdircria um diretório se ele ainda não existe e, se existir, define um código de saída. Mais importante, ele faz tudo isso em uma única ação atômica, tornando-o perfeito para esse cenário.
Se você deseja cuidar de bloqueios obsoletos, o fusor (1) é útil. A única desvantagem aqui é que a operação leva cerca de um segundo, portanto não é instantânea.
Aqui está uma função que escrevi uma vez que resolve o problema usando o fusor:
# mutex file## Open a mutual exclusion lock on the file, unless another process already owns one.## If the file is already locked by another process, the operation fails.# This function defines a lock on a file as having a file descriptor open to the file.# This function uses FD 9 to open a lock on the file. To release the lock, close FD 9:# exec 9>&-#
mutex(){local file=$1 pid pids
exec 9>>"$file"{ pids=$(fuser -f "$file");}2>&-9>&-for pid in $pids;do[[ $pid = $$ ]]&&continue
exec 9>&-return1# Locked by a pid.done}
Se você não se importa com portabilidade (essas soluções devem funcionar com praticamente qualquer caixa UNIX), o fusor do Linux (1) oferece algumas opções adicionais e também há o flock (1) .
Você pode combinar a if ! mkdirpeça com a verificação de se o processo com o PID armazenado (na inicialização bem-sucedida) dentro do lockdir está realmente em execução e é idêntico ao script para proteção dos stalenes. Isso também protegeria contra a reutilização do PID após uma reinicialização e nem sequer exigiria fuser.
Tobias Kienzler
4
Certamente é verdade que mkdirnão está definido como uma operação atômica e, como tal, "efeito colateral" é um detalhe de implementação do sistema de arquivos. Eu acredito plenamente nele, se ele disser que o NFS não o implementa de maneira atômica. Embora eu não suspeite que você /tmpseja um compartilhamento NFS e provavelmente será fornecido por um fs que é implementado mkdiratomicamente.
Lhunath 19/09/12
5
Mas há uma maneira de verificar a existência de um arquivo regular e criá-lo atomicamente, se não existir: use lnpara criar um link físico a partir de outro arquivo. Se você tiver sistemas de arquivos estranhos que não garantem isso, poderá verificar o inode do novo arquivo posteriormente para ver se é o mesmo que o arquivo original.
Juan Cespedes
4
Não é 'uma maneira de verificar se um arquivo existe e criá-lo em uma única ação atômica' - é open(... O_CREAT|O_EXCL). Você só precisa de um programa de usuário adequado, como lockfile-create(in lockfile-progs) ou dotlockfile(in liblockfile-bin). E certifique-se de limpar corretamente (por exemplo trap EXIT) ou testar bloqueios obsoletos (por exemplo, com --use-pid).
Toby Speight
5
"Todas as abordagens que testam a existência de" arquivos de bloqueio "são defeituosas. Por quê? Como não há como verificar se existe um arquivo e criá-lo em uma única ação atômica." - Para torná-lo atômico, isso deve ser feito em o nível do kernel - e é feito no nível do kernel com o flock (1) linux.die.net/man/1/flock, que parece ter existido desde a data de copyright do homem desde, pelo menos, 2006. Então fiz um voto negativo (- 1), nada pessoal, apenas tenha forte convicção de que o uso das ferramentas implementadas pelo kernel fornecidas pelos desenvolvedores do kernel está correto.
Craig Hicks
42
Há um invólucro ao redor da chamada do sistema flock (2) chamado, sem imaginação, flock (1). Isso torna relativamente fácil obter bloqueios exclusivos de maneira confiável, sem se preocupar com a limpeza etc. Existem exemplos na página do manual sobre como usá-lo em um script de shell.
A flock()chamada do sistema não é POSIX e não funciona para arquivos em montagens NFS.
maxschlepzig
17
Executando a partir de um trabalho Cron que utilizo flock -x -n %lock file% -c "%command%"para garantir que apenas uma instância esteja em execução.
Ryall
Aww, em vez do rebanho sem imaginação (1), eles deveriam ter seguido algo como o rebanho (U). ... tem alguma familiaridade com isso. . .parece que eu já ouvi isso antes de uma ou duas vezes.
Kent Kruckeberg
É notável que a documentação do flock (2) especifique o uso apenas com arquivos, mas a documentação do flock (1) especifique o uso com o arquivo ou o diretório. A documentação do flock (1) não é explícita sobre como indicar a diferença durante a criação, mas presumo que isso seja feito adicionando um "/" final. De qualquer forma, se o flock (1) pode manipular diretórios, mas o flock (2) não, o flock (1) não é implementado apenas no flock (2).
Craig Hicks
27
Você precisa de uma operação atômica, como o rebanho, caso contrário, isso acabará por falhar.
Mas o que fazer se o rebanho não estiver disponível. Bem, há mkdir. Essa é uma operação atômica também. Apenas um processo resultará em um mkdir bem-sucedido, todos os outros falharão.
Então o código é:
if mkdir /var/lock/.myscript.exclusivelock
then# do stuff:
rmdir /var/lock/.myscript.exclusivelock
fi
Você precisa cuidar dos bloqueios obsoletos após uma falha, seu script nunca será executado novamente.
Execute isso algumas vezes simultaneamente (como "./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ./a.sh & ") e o script vazará algumas vezes.
Nippysaurus
7
@ Nippysaurus: Este método de bloqueio não vaza. O que você viu foi o script inicial terminando antes de todas as cópias serem lançadas, para que outro pudesse (corretamente) obter o bloqueio. Para evitar esse falso positivo, adicione um sleep 10antes rmdire tente cascatear novamente - nada "vazará".
Sir Athos
Outras fontes afirmam que o mkdir não é atômico em alguns sistemas de arquivos como o NFS. E já vi ocasiões em que no NFS o mkdir recursivo simultâneo leva a erros às vezes nos trabalhos de matriz de jenkins. Então, eu tenho certeza que é esse o caso. Mas mkdir é muito bom para IMO de casos de uso menos exigentes.
Akostadinov
Você pode usar a opção nashboard de Bash'es com arquivos regulares.
Palec
26
Para tornar o bloqueio confiável, você precisa de uma operação atômica. Muitas das propostas acima não são atômicas. O utilitário lockfile (1) proposto parece promissor, como mencionado na página de manual, que é "resistente ao NFS". Se o seu sistema operacional não suporta o arquivo de bloqueio (1) e sua solução precisa funcionar no NFS, você não tem muitas opções ....
O NFSv2 possui duas operações atômicas:
ligação simbólica
renomear
Com o NFSv3, a chamada de criação também é atômica.
As operações de diretório NÃO são atômicas nos NFSv2 e NFSv3 (consulte o livro 'NFS Illustrated' de Brent Callaghan, ISBN 0-201-32570-5; Brent é um veterano do NFS na Sun).
Sabendo disso, você pode implementar bloqueios de rotação para arquivos e diretórios (no shell, não no PHP):
dir dir atual:
while! ln -s . lock;do:;done
bloquear um arquivo:
while! ln -s ${f} ${f}.lock;do:;done
desbloquear dir atual (suposição, o processo em execução realmente adquiriu o bloqueio):
mv lock deleteme && rm deleteme
desbloquear um arquivo (suposição, o processo em execução realmente adquiriu o bloqueio):
mv ${f}.lock ${f}.deleteme && rm ${f}.deleteme
Remover também não é atômico; portanto, primeiro renomeie (que é atômico) e depois remova.
Para as chamadas de link simbólico e renomeação, os dois nomes de arquivos devem residir no mesmo sistema de arquivos. Minha proposta: use apenas nomes de arquivos simples (sem caminhos) e coloque o arquivo e bloqueie no mesmo diretório.
Quais páginas do NFS Illustrated suportam a afirmação de que o mkdir não é atômico sobre o NFS?
maxschlepzig
Agradece por esta técnica. Uma implementação do shell mutex está disponível na minha nova lib shell: github.com/Offirmo/offirmo-shell-lib , consulte "mutex". Ele usa, lockfilese disponível, ou fallback para esse symlinkmétodo, se não.
Offirmo
Agradável. Infelizmente, esse método não fornece uma maneira de excluir automaticamente bloqueios obsoletos.
Richard Hansen
Para o desbloqueio em dois estágios ( mv, rm), deve rm -fser usado, e não rmno caso de dois processos P1, P2 estarem correndo? Por exemplo, P1 começa a desbloquear com mv, em seguida, P2 bloqueia e, em seguida, P2 desbloqueia (ambos mve rm), finalmente P1 tenta rme falha.
Matt Wallis
1
@ MattWallis Esse último problema pode ser facilmente mitigado com a inclusão $$no ${f}.deletemenome do arquivo.
Stefan Majewsky
23
Outra opção é usar a noclobberopção do shell executando set -C. Então >falhará se o arquivo já existir.
Em resumo:
set-C
lockfile="/tmp/locktest.lock"if echo "$$">"$lockfile";then
echo "Successfully acquired lock"# do work
rm "$lockfile"# XXX or via trap - see belowelse
echo "Cannot acquire lock - already locked by $(cat "$lockfile")"fi
Isso faz com que o shell chame:
open(pathname, O_CREAT|O_EXCL)
que cria atomicamente o arquivo ou falha se o arquivo já existe.
De acordo com um comentário no BashFAQ 045 , isso pode falhar ksh88, mas funciona em todas as minhas conchas:
Abordagem muito nova ... essa parece ser uma maneira de obter atomicidade usando um arquivo de bloqueio em vez de um diretório de bloqueio.
Matt Caldwell
Boa abordagem. :-) Na armadilha EXIT, deve restringir qual processo pode limpar o arquivo de bloqueio. Por exemplo: trap 'if [[$ (cat "$ lockfile") == "$$"]]; então rm "$ lockfile"; fi 'EXIT
Kevin Seifert
1
Os arquivos de bloqueio não são atômicos sobre o NFS. é por isso que as pessoas passaram a usar diretórios de bloqueio.
K Richard Pixley
20
Você pode usar GNU Parallelisso, pois ele funciona como um mutex quando chamado como sem. Portanto, em termos concretos, você pode usar:
sem --id SCRIPTSINGLETON yourScript
Se você deseja um tempo limite também, use:
sem --id SCRIPTSINGLETON --semaphoretimeout -10 yourScript
Tempo limite de <0 significa sair sem executar o script se o semáforo não for liberado dentro do tempo limite, tempo limite de> 0 significa executar o script de qualquer maneira.
Observe que você deve dar um nome (com --id) ou o padrão é o terminal de controle.
GNU Parallel é uma instalação muito simples na maioria das plataformas Linux / OSX / Unix - é apenas um script Perl.
Pena que as pessoas relutam em votar respostas inúteis: isso leva a que novas respostas relevantes sejam enterradas em uma pilha de lixo.
Dmitry Grigoryev
4
Nós só precisamos de muitos votos. Essa é uma resposta tão organizada e pouco conhecida. (Embora fosse um pedante, o OP queria ser rápido e sujo, enquanto isso é rápido e limpo!) Mais sobre sema questão relacionada unix.stackexchange.com/a/322200/199525 .
Parcialmente nublado
16
Para scripts de shell, costumo usar o mkdirover flock, pois torna os bloqueios mais portáteis.
De qualquer maneira, usar set -enão é suficiente. Isso sai do script apenas se algum comando falhar. Seus bloqueios ainda serão deixados para trás.
Para uma limpeza adequada dos bloqueios, você realmente deve definir seus traps para algo como este código psuedo (elevado, simplificado e não testado, mas com scripts usados ativamente):
#=======================================================================# Predefined Global Variables#=======================================================================
TMPDIR=/tmp/myapp
[[!-d $TMP_DIR ]] \
&& mkdir -p $TMP_DIR \
&& chmod 700 $TMPDIR
LOCK_DIR=$TMP_DIR/lock
#=======================================================================# Functions#=======================================================================function mklock {
__lockdir="$LOCK_DIR/$(date +%s.%N).$$"# Private Global. Use Epoch.Nano.PID# If it can create $LOCK_DIR then no other instance is runningif $(mkdir $LOCK_DIR)then
mkdir $__lockdir # create this instance's specific lock in queue
LOCK_EXISTS=true # Globalelse
echo "FATAL: Lock already exists. Another copy is running or manually lock clean up required."
exit 1001# Or work out some sleep_while_execution_lock elsewherefi}function rmlock {[[!-d $__lockdir ]] \
&& echo "WARNING: Lock is missing. $__lockdir does not exist" \
|| rmdir $__lockdir
}#-----------------------------------------------------------------------# Private Signal Traps Functions {{{2## DANGER: SIGKILL cannot be trapped. So, try not to `kill -9 PID` or # there will be *NO CLEAN UP*. You'll have to manually remove # any locks in place.#-----------------------------------------------------------------------function __sig_exit {# Place your clean up logic here # Remove the LOCK[[-n $LOCK_EXISTS ]]&& rmlock
}function __sig_int {
echo "WARNING: SIGINT caught"
exit 1002}function __sig_quit {
echo "SIGQUIT caught"
exit 1003}function __sig_term {
echo "WARNING: SIGTERM caught"
exit 1015}#=======================================================================# Main#=======================================================================# Set TRAPs
trap __sig_exit EXIT # SIGEXIT
trap __sig_int INT # SIGINT
trap __sig_quit QUIT # SIGQUIT
trap __sig_term TERM # SIGTERM
mklock
# CODE
exit # No need for cleanup code here being in the __sig_exit trap function
Aqui está o que vai acontecer. Todas as armadilhas produzirão uma saída, para que a função __sig_exitsempre aconteça (com exceção de um SIGKILL), que limpa seus bloqueios.
Nota: meus valores de saída não são baixos. Por quê? Vários sistemas de processamento em lote atendem ou têm expectativas dos números de 0 a 31. Configurando-os para outra coisa, posso fazer com que meus scripts e fluxos de lote reajam de acordo com o trabalho ou script em lote anterior.
Seu script é muito detalhado, acho que poderia ter sido muito mais curto, mas no geral, sim, você precisa configurar armadilhas para fazer isso corretamente. Além disso, eu adicionaria SIGHUP.
Mjuba
Isso funciona bem, exceto que parece verificar $ LOCK_DIR enquanto remove $ __ lockdir. Talvez eu devesse sugerir que, ao remover o bloqueio, você faria rm -r $ LOCK_DIR?
bevada
Obrigado pela sugestão. O código acima foi levantado e colocado em um código de psuedo, portanto, será necessário um ajuste com base no uso das pessoas. No entanto, eu fui deliberadamente com o rmdir no meu caso, pois o rmdir remove com segurança apenas os diretórios - se estiverem vazios. Se as pessoas estão colocando recursos neles, como arquivos PID, etc., eles devem alterar a limpeza de seus bloqueios para torná-los mais agressivos rm -r $LOCK_DIRou até forçá-los conforme necessário (como fiz também em casos especiais, como manter arquivos de rascunho relativos). Felicidades.
Mark Stinson
Você já testou exit 1002?
Gilles Quenot
13
Muito rápido e muito sujo? Esta linha única na parte superior do seu script funcionará:
[[ $(pgrep -c "`basename \"$0\"`")-gt 1]]&& exit
Obviamente, apenas verifique se o nome do seu script é único. :)
Como faço para simular isso para testá-lo? Existe uma maneira de iniciar um script duas vezes em uma linha e talvez receber um aviso, se ele já estiver em execução?
rubo77
2
Isso não está funcionando! Por que verificar -gt 2? O grep nem sempre se encontra no resultado do ps!
rubo77
pgrepnão está no POSIX. Se você deseja que isso funcione de maneira portável, é necessário o POSIX pse processar sua saída.
Palec 31/07
No OSX -cnão existe, você terá que usar | wc -l. Sobre a comparação de números: -gt 1está marcado desde que a primeira instância se vê.
Benjamin Peter
6
Aqui está uma abordagem que combina o bloqueio de diretório atômico com uma verificação de bloqueio obsoleto via PID e reinicia se obsoleto. Além disso, isso não depende de nenhum basismo.
#!/bin/dash
SCRIPTNAME=$(basename $0)
LOCKDIR="/var/lock/${SCRIPTNAME}"
PIDFILE="${LOCKDIR}/pid"if! mkdir $LOCKDIR 2>/dev/null
then# lock failed, but check for stale one by checking if the PID is really existing
PID=$(cat $PIDFILE)if! kill -0 $PID 2>/dev/null
then
echo "Removing stale lock of nonexistent PID ${PID}">&2
rm -rf $LOCKDIR
echo "Restarting myself (${SCRIPTNAME})">&2
exec "$0""$@"fi
echo "$SCRIPTNAME is already running, bailing out">&2
exit 1else# lock successfully acquired, save PID
echo $$ > $PIDFILE
fi
trap "rm -rf ${LOCKDIR}" QUIT INT TERM EXIT
echo hello
sleep 30s
echo bye
Criar um arquivo de bloqueio em um local conhecido e verificar a existência no início do script? Colocar o PID no arquivo pode ser útil se alguém tentar rastrear uma instância incorreta que esteja impedindo a execução do script.
Este exemplo é explicado no man rebanho, mas precisa de alguns aprimoramentos, porque devemos gerenciar bugs e códigos de saída:
#!/bin/bash#set -e this is useful only for very stupid scripts because script fails when anything command exits with status more than 0 !! without possibility for capture exit codes. not all commands exits >0 are failed.(#start subprocess# Wait for lock on /var/lock/.myscript.exclusivelock (fd 200) for 10 seconds
flock -x -w 10200if["$?"!="0"];then echo Cannot lock!; exit 1;fi
echo $$>>/var/lock/.myscript.exclusivelock #for backward lockdir compatibility, notice this command is executed AFTER command bottom ) 200>/var/lock/.myscript.exclusivelock.# Do stuff# you can properly manage exit codes with multiple command and process algorithm.# I suggest throw this all to external procedure than can properly handle exit X commands)200>/var/lock/.myscript.exclusivelock #exit subprocess
FLOCKEXIT=$?#save exitcode status#do some finish commands
exit $FLOCKEXIT #return properly exitcode, may be usefull inside external scripts
Você pode usar outro método, listar processos que eu usei no passado. Mas isso é mais complicado que o método acima. Você deve listar os processos por ps, filtrar por seu nome, filtro adicional grep -v grep para remover o parasita e, finalmente, contá-lo por grep -c. e compare com o número. É complicado e incerto
Você pode usar ln -s, porque isso pode criar um link simbólico apenas quando não houver arquivo ou link simbólico, o mesmo que mkdir. muitos processos do sistema usavam links simbólicos no passado, por exemplo, init ou inetd. O synlink mantém a identificação do processo, mas realmente não aponta para nada. durante os anos esse comportamento foi alterado. processos usa bandos e semáforos.
Znik 14/08
5
As respostas existentes publicadas dependem do utilitário CLI flockou não protegem adequadamente o arquivo de bloqueio. O utilitário flock não está disponível em todos os sistemas não Linux (por exemplo, FreeBSD) e não funciona corretamente no NFS.
Nos meus primeiros dias de administração e desenvolvimento do sistema, me disseram que um método seguro e relativamente portátil de criar um arquivo de bloqueio era criar um arquivo temporário usando mkemp(3)ou mkemp(1), gravando informações de identificação no arquivo temporário (ou seja, PID) e, em seguida, vinculando o hardware o arquivo temporário para o arquivo de bloqueio. Se o link foi bem-sucedido, você obteve o bloqueio com êxito.
Ao usar bloqueios em scripts de shell, normalmente coloco uma obtain_lock()função em um perfil compartilhado e depois a origino dos scripts. Abaixo está um exemplo da minha função de bloqueio:
obtain_lock(){
LOCK="${1}"
LOCKDIR="$(dirname "${LOCK}")"
LOCKFILE="$(basename "${LOCK}")"# create temp lock file
TMPLOCK=$(mktemp -p "${LOCKDIR}""${LOCKFILE}XXXXXX"2>/dev/null)if test "x${TMPLOCK}"=="x";then
echo "unable to create temporary file with mktemp"1>&2return1fi
echo "$$">"${TMPLOCK}"# attempt to obtain lock file
ln "${TMPLOCK}""${LOCK}"2>/dev/null
if test $?-ne 0;then
rm -f "${TMPLOCK}"
echo "unable to obtain lockfile"1>&2if test -f "${LOCK}";then
echo "current lock information held by: $(cat "${LOCK}")"1>&2fireturn2fi
rm -f "${TMPLOCK}"return0;};
A seguir, é apresentado um exemplo de como usar a função de bloqueio:
#!/bin/sh./path/to/locking/profile.sh
PROG_LOCKFILE="/tmp/myprog.lock"
clean_up(){
rm -f "${PROG_LOCKFILE}"}
obtain_lock "${PROG_LOCKFILE}"if test $?-ne 0;then
exit 1fi
trap clean_up SIGHUP SIGINT SIGTERM
# bulk of script
clean_up
exit 0# end of script
Lembre-se de ligar clean_upem qualquer ponto de saída do seu script.
Eu usei o acima em ambos os ambientes Linux e FreeBSD.
Ao direcionar uma máquina Debian, acho o lockfile-progspacote uma boa solução. procmailtambém vem com uma lockfileferramenta. No entanto, às vezes eu estou preso com nenhum deles.
Aqui está minha solução que usa mkdiratômica e um arquivo PID para detectar bloqueios obsoletos. Atualmente, esse código está em produção em uma configuração do Cygwin e funciona bem.
Para usá-lo, basta ligar exclusive_lock_requirequando precisar obter acesso exclusivo a algo. Um parâmetro opcional de nome de bloqueio permite compartilhar bloqueios entre diferentes scripts. Há também duas funções de nível inferior ( exclusive_lock_trye exclusive_lock_retry), caso você precise de algo mais complexo.
function exclusive_lock_try()# [lockname]{local LOCK_NAME="${1:-`basename $0`}"
LOCK_DIR="/tmp/.${LOCK_NAME}.lock"local LOCK_PID_FILE="${LOCK_DIR}/${LOCK_NAME}.pid"if[-e "$LOCK_DIR"]thenlocal LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"if[!-z "$LOCK_PID"]&& kill -0"$LOCK_PID"2>/dev/null
then# locked by non-dead process
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return1else# orphaned lock, take it over( echo $$ >"$LOCK_PID_FILE")2>/dev/null &&local LOCK_PID="$$"fifiif["`trap -p EXIT`"!=""]then# already have an EXIT trap
echo "Cannot get lock, already have an EXIT trap"return1fiif["$LOCK_PID"!="$$"]&&!( umask 077&& mkdir "$LOCK_DIR"&& umask 177&& echo $$ >"$LOCK_PID_FILE")2>/dev/null
thenlocal LOCK_PID="`cat "$LOCK_PID_FILE" 2> /dev/null`"# unable to acquire lock, new process got in first
echo "\"$LOCK_NAME\" lock currently held by PID $LOCK_PID"return1fi
trap "/bin/rm -rf \"$LOCK_DIR\"; exit;" EXIT
return0# got lock}function exclusive_lock_retry()# [lockname] [retries] [delay]{local LOCK_NAME="$1"local MAX_TRIES="${2:-5}"local DELAY="${3:-2}"local TRIES=0local LOCK_RETVAL
while["$TRIES"-lt "$MAX_TRIES"]doif["$TRIES"-gt 0]then
sleep "$DELAY"filocal TRIES=$(( $TRIES +1))if["$TRIES"-lt "$MAX_TRIES"]then
exclusive_lock_try "$LOCK_NAME">/dev/null
else
exclusive_lock_try "$LOCK_NAME"fi
LOCK_RETVAL="${PIPESTATUS[0]}"if["$LOCK_RETVAL"-eq 0]thenreturn0fidonereturn"$LOCK_RETVAL"}function exclusive_lock_require()# [lockname] [retries] [delay]{if! exclusive_lock_retry "$@"then
exit 1fi}
Obrigado, tentei no cygwin eu mesmo e passou em testes simples.
Ndemou
4
Se as limitações do rebanho, que já foram descritas em outras partes deste segmento, não são um problema para você, isso deve funcionar:
#!/bin/bash{# exit if we are unable to obtain a lock; this would happen if # the script is already running elsewhere# note: -x (exclusive) is the default
flock -n 100|| exit
# put commands to run here
sleep 100}100>/tmp/myjob.lock
Apenas pensei em apontar que -x (bloqueio de gravação) já está definido por padrão.
Keldon Alleyne '
e -nirá exit 1imediatamente se não puder obter o bloqueio
Anentropic
Obrigado @KeldonAlleyne, atualizei o código para remover "-x", pois é o padrão.
presto8
3
Alguns unixes possuem lockfilemuito semelhante ao já mencionado flock.
Na página de manual:
O lockfile pode ser usado para criar um ou mais arquivos de semáforo. Se o arquivo de bloqueio não puder criar todos os arquivos especificados (na ordem especificada), ele aguardará o tempo de suspensão (o padrão é 8) segundos e tentará novamente o último arquivo que não teve êxito. Você pode especificar o número de novas tentativas até que a falha seja retornada. Se o número de tentativas for -1 (padrão, ou seja, -r-1), o arquivo de bloqueio tentará novamente para sempre.
lockfileé distribuído com procmail. Também há uma alternativa dotlockfileque acompanha o liblockfilepacote. Ambos afirmam trabalhar de maneira confiável no NFS.
Mr. Deathless
3
Na verdade, embora a resposta do bmdhacks seja quase boa, há uma pequena chance de o segundo script ser executado depois de verificar primeiro o arquivo de bloqueio e antes de escrevê-lo. Então, ambos escreverão o arquivo de bloqueio e estarão executando. Aqui está como fazê-lo funcionar com certeza:
lockfile=/var/lock/myscript.lock
if(set-o noclobber; echo "$$">"$lockfile")2>/dev/null ;then
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
else# or you can decide to skip the "else" part if you want
echo "Another instance is already running!"fi
A noclobberopção garantirá que o comando de redirecionamento falhe se o arquivo já existir. Portanto, o comando de redirecionamento é realmente atômico - você escreve e verifica o arquivo com um comando. Você não precisa remover o arquivo de bloqueio no final do arquivo - ele será removido pela armadilha. Espero que isso ajude as pessoas que o lerão mais tarde.
PS: Não vi que Mikel já respondesse a pergunta corretamente, embora ele não incluísse o comando trap para reduzir a chance de o arquivo de bloqueio ser deixado após a interrupção do script com Ctrl-C, por exemplo. Portanto, esta é a solução completa
Eu queria acabar com arquivos de bloqueio, lockdirs, programas especiais de bloqueio e, mesmo pidofque não sejam encontrados em todas as instalações do Linux. Também queria ter o código mais simples possível (ou pelo menos o menor número possível de linhas). ifDeclaração mais simples , em uma linha:
Isso é sensível à saída 'ps', na minha máquina (Ubuntu 14.04, / bin / ps da procps-ng versão 3.3.9) o comando 'ps axf' imprime caracteres de árvore ascii que atrapalham os números de campo. Isso funcionou para mim: /bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
qneill 23/02
2
Eu uso uma abordagem simples que lida com arquivos de bloqueio obsoletos.
Observe que algumas das soluções acima que armazenam o pid, ignoram o fato de que o pid pode ser contornado. Portanto, apenas verificar se existe um processo válido com o pid armazenado não é suficiente, especialmente para scripts de execução longa.
Eu uso o noclobber para garantir que apenas um script possa abrir e gravar no arquivo de bloqueio de uma vez. Além disso, armazeno informações suficientes para identificar exclusivamente um processo no arquivo de bloqueio. Defino o conjunto de dados para identificar exclusivamente um processo a ser pid, ppid, lstart.
Quando um novo script é iniciado, se não conseguir criar o arquivo de bloqueio, ele verifica se o processo que criou o arquivo de bloqueio ainda está por aí. Caso contrário, presumimos que o processo original tenha sofrido uma morte sem graça e deixamos um arquivo de bloqueio obsoleto. O novo script assume a propriedade do arquivo de bloqueio e tudo está bem no mundo novamente.
Deve funcionar com vários shells em várias plataformas. Rápido, portátil e simples.
#!/usr/bin/env sh# Author: rouble
LOCKFILE=/var/tmp/lockfile #customize this line
trap release INT TERM EXIT
# Creates a lockfile. Sets global variable $ACQUIRED to true on success.# # Returns 0 if it is successfully able to create lockfile.
acquire (){set-C #Shell noclobber option. If file exists, > will fail.
UUID=`ps -eo pid,ppid,lstart $$ | tail -1`if(echo "$UUID">"$LOCKFILE")2>/dev/null;then
ACQUIRED="TRUE"return0elseif[-e $LOCKFILE ];then# We may be dealing with a stale lock file.# Bring out the magnifying glass.
CURRENT_UUID_FROM_LOCKFILE=`cat $LOCKFILE`
CURRENT_PID_FROM_LOCKFILE=`cat $LOCKFILE | cut -f 1 -d " "`
CURRENT_UUID_FROM_PS=`ps -eo pid,ppid,lstart $CURRENT_PID_FROM_LOCKFILE | tail -1`if["$CURRENT_UUID_FROM_LOCKFILE"=="$CURRENT_UUID_FROM_PS"];then
echo "Script already running with following identification: $CURRENT_UUID_FROM_LOCKFILE">&2return1else# The process that created this lock file died an ungraceful death. # Take ownership of the lock file.
echo "The process $CURRENT_UUID_FROM_LOCKFILE is no longer around. Taking ownership of $LOCKFILE"
release "FORCE"if(echo "$UUID">"$LOCKFILE")2>/dev/null;then
ACQUIRED="TRUE"return0else
echo "Cannot write to $LOCKFILE. Error.">&2return1fifielse
echo "Do you have write permissons to $LOCKFILE ?">&2return1fifi}# Removes the lock file only if this script created it ($ACQUIRED is set), # OR, if we are removing a stale lock file (first parameter is "FORCE")
release (){#Destroy lock file. Take no prisoners.if["$ACQUIRED"]||["$1"=="FORCE"];then
rm -f $LOCKFILE
fi}# Test code# int main( int argc, const char* argv[] )
echo "Acquring lock."
acquire
if[ $?-eq 0];then
echo "Acquired lock."
read -p "Press [Enter] key to release lock..."
release
echo "Released lock."else
echo "Unable to acquire lock."fi
Eu te dei +1 para uma solução diferente. Embora não funcione no AIX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: lista inválida com -o.) Não no HP-UX (> ps -eo pid, ppid, lstart $$ | tail -1 ps: opção ilegal - o). Obrigado.
["${FLOCKER}"!="$0"]&&{ echo "Trying to start build from queue... "; exec bash -c "FLOCKER='$0' flock -E $E_LOCKED -en '$0' '$0' '$@' || if [ \"\$?\" -eq $E_LOCKED ]; then echo 'Locked.'; fi";}|| echo "Lock is free. Completing."
Isso define e verifica bloqueios usando flock utilitário. Esse código detecta se foi executado pela primeira vez verificando a variável FLOCKER, se não estiver definido como o nome do script, tenta iniciar o script novamente recursivamente usando flock e com a variável FLOCKER inicializada, se FLOCKER estiver definido corretamente, e em seguida na iteração anterior foi bem-sucedido e não há problema em prosseguir. Se o bloqueio estiver ocupado, ele falhará com o código de saída configurável.
Parece não funcionar no Debian 7, mas parece funcionar novamente com o pacote experimental util-linux 2.25. Ele escreve "rebanho: ... Arquivo de texto ocupado". Isso pode ser substituído desativando a permissão de gravação no seu script.
PID e arquivos de bloqueio são definitivamente os mais confiáveis. Quando você tenta executar o programa, ele pode verificar o arquivo de bloqueio que, se existir, pode ser usado pspara verificar se o processo ainda está em execução. Caso contrário, o script pode iniciar, atualizando o PID no arquivo de bloqueio para seu próprio.
Acho que a solução do bmdhack é a mais prática, pelo menos para o meu caso de uso. O uso de flock e lockfile depende da remoção do arquivo de bloqueio usando rm quando o script termina, o que nem sempre pode ser garantido (por exemplo, kill -9).
Eu mudaria um pouco da solução do bmdhack: faz questão de remover o arquivo de bloqueio, sem afirmar que isso é desnecessário para o trabalho seguro desse semáforo. Seu uso de kill -0 garante que um arquivo de bloqueio antigo para um processo morto seja simplesmente ignorado / sobrescrito.
Minha solução simplificada é, portanto, simplesmente adicionar o seguinte ao topo do seu singleton:
## Test the lock
LOCKFILE=/tmp/singleton.lock
if[-e ${LOCKFILE}]&& kill -0`cat ${LOCKFILE}`;then
echo "Script already running. bye!"
exit
fi## Set the lock
echo $$ > ${LOCKFILE}
Obviamente, esse script ainda tem a falha de que os processos que provavelmente iniciarão ao mesmo tempo apresentam um risco de corrida, pois o teste de bloqueio e as operações de conjunto não são uma ação atômica única. Mas a solução proposta pelo lhunath para usar o mkdir tem a falha que um script morto pode deixar para trás do diretório, impedindo a execução de outras instâncias.
O utilitário semáforo usa flock(como discutido acima, por exemplo, presto8) para implementar um semáforo de contagem . Ele permite qualquer número específico de processos simultâneos que você deseja. Nós o usamos para limitar o nível de simultaneidade de vários processos do operador de fila.
É como sem, mas muito mais leve. (Divulgação completa: escrevi depois de descobrir que o sem era muito pesado para nossas necessidades e não havia um utilitário simples de semáforo de contagem disponível.)
Um exemplo com o flock (1), mas sem subshell. O arquivo flock () ed / tmp / foo nunca é removido, mas isso não importa, pois ele é flock () e un-flock () ed.
#!/bin/bash
exec 9<>/tmp/foo
flock -n 9
RET=$?if[[ $RET -ne 0]];then
echo "lock failed, exiting"
exit
fi#Now we are inside the "critical section"
echo "inside lock"
sleep 5
exec 9>&-#close fd 9, and release lock#The part below is outside the critical section (the lock)
echo "lock released"
sleep 5
Cada vez que ele grava o PID atual ($$) no arquivo de bloqueio e na inicialização do script, verifica se um processo está sendo executado com o PID mais recente.
Sem a chamada de interceptação (ou pelo menos uma limpeza próxima ao final para o caso normal), você tem o bug falso positivo onde o arquivo de bloqueio é deixado após a última execução e o PID foi reutilizado por outro processo posteriormente. (E, na pior das hipóteses, ele foi dotado de um longo processo correndo como apache ....)
Philippe Chaintreuil
1
Concordo, minha abordagem é falha, precisa de uma armadilha. Eu atualizei minha solução. Eu ainda prefiro não ter dependências externas.
Filidor Wiese
1
Usar o bloqueio do processo é muito mais forte e também cuida das saídas desagradáveis. lock_file é mantido aberto enquanto o processo estiver em execução. Ele será fechado (pelo shell) assim que o processo existir (mesmo que seja morto). Eu achei isso muito eficiente:
lock_file=/tmp/`basename $0`.lock
if fuser $lock_file >/dev/null 2>&1;then
echo "WARNING: Other instance of $(basename $0) running."
exit 1fi
exec 3> $lock_file
O caminho do rebanho é o caminho a percorrer. Pense no que acontece quando o script morre repentinamente. No caso do rebanho, você apenas perde o rebanho, mas isso não é um problema. Além disso, observe que um truque maléfico é atacar o próprio script ... mas isso obviamente permite que você corra a todo vapor em problemas de permissão.
Isso pode ou não ser um problema, dependendo de como é usado, mas há uma condição de corrida entre testar o bloqueio e criá-lo, para que dois scripts possam ser iniciados ao mesmo tempo. Se um terminar primeiro, o outro continuará sendo executado sem um arquivo de bloqueio.
TimB
3
O C News, que me ensinou muito sobre scripts de shell portáteis, costumava criar um arquivo. $$ de bloqueio e, em seguida, tentar vinculá-lo a "bloqueio" - se o link foi bem-sucedido, você tinha o bloqueio, caso contrário, você removeu o bloqueio. $$ e saiu.
Paul Tomblin 9/10/08
Essa é realmente uma boa maneira de fazê-lo, exceto que você ainda sofre com a necessidade de remover o arquivo de bloqueio manualmente se algo der errado e o arquivo de bloqueio não for excluído.
Respostas:
Aqui está uma implementação que usa um arquivo de bloqueio e faz eco de um PID nele. Isso serve como uma proteção se o processo for morto antes de remover o pidfile :
O truque aqui é o
kill -0
que não entrega nenhum sinal, mas apenas verifica se existe um processo com o PID especificado. Além disso, a chamada paratrap
garantirá que o arquivo de bloqueio seja removido mesmo quando seu processo for encerrado (excetokill -9
).fonte
Use
flock(1)
para tornar um bloqueio de escopo exclusivo um descritor de arquivo. Dessa forma, você pode até sincronizar diferentes partes do script.Isso garante que o código entre
(
e)
seja executado apenas por um processo por vez e que o processo não espere muito tempo por um bloqueio.Advertência: este comando em particular faz parte
util-linux
. Se você executar um sistema operacional diferente do Linux, ele pode ou não estar disponível.fonte
( command A ) command B
chama um subshell paracommand A
. Documentado em tldp.org/LDP/abs/html/subshells.html . Eu ainda não estou certo sobre o momento da invocação do subshell e comando B.if flock -x -w 10 200; then ...Do stuff...; else echo "Failed to lock file" 1>&2; fi
para que, se ocorrer o tempo limite (algum outro processo tiver o arquivo bloqueado), esse script não avance e modifique o arquivo. Provavelmente ... o contra-argumento é 'mas se demorou 10 segundos e o bloqueio ainda não está disponível, nunca estará disponível', provavelmente porque o processo que contém o bloqueio não está terminando (talvez esteja sendo executado sob um depurador?).exit
é da parte dentro do(
)
. Quando o subprocesso termina, o bloqueio é liberado automaticamente, porque não há processo mantendo-o.Todas as abordagens que testam a existência de "arquivos de bloqueio" são falhas.
Por quê? Porque não há como verificar se existe um arquivo e criá-lo em uma única ação atômica. Por causa disso; há uma condição de corrida que vai fazer as suas tentativas de quebra de exclusão mútua.
Em vez disso, você precisa usar
mkdir
.mkdir
cria um diretório se ele ainda não existe e, se existir, define um código de saída. Mais importante, ele faz tudo isso em uma única ação atômica, tornando-o perfeito para esse cenário.Para todos os detalhes, consulte o excelente BashFAQ: http://mywiki.wooledge.org/BashFAQ/045
Se você deseja cuidar de bloqueios obsoletos, o fusor (1) é útil. A única desvantagem aqui é que a operação leva cerca de um segundo, portanto não é instantânea.
Aqui está uma função que escrevi uma vez que resolve o problema usando o fusor:
Você pode usá-lo em um script como este:
Se você não se importa com portabilidade (essas soluções devem funcionar com praticamente qualquer caixa UNIX), o fusor do Linux (1) oferece algumas opções adicionais e também há o flock (1) .
fonte
if ! mkdir
peça com a verificação de se o processo com o PID armazenado (na inicialização bem-sucedida) dentro do lockdir está realmente em execução e é idêntico ao script para proteção dos stalenes. Isso também protegeria contra a reutilização do PID após uma reinicialização e nem sequer exigiriafuser
.mkdir
não está definido como uma operação atômica e, como tal, "efeito colateral" é um detalhe de implementação do sistema de arquivos. Eu acredito plenamente nele, se ele disser que o NFS não o implementa de maneira atômica. Embora eu não suspeite que você/tmp
seja um compartilhamento NFS e provavelmente será fornecido por um fs que é implementadomkdir
atomicamente.ln
para criar um link físico a partir de outro arquivo. Se você tiver sistemas de arquivos estranhos que não garantem isso, poderá verificar o inode do novo arquivo posteriormente para ver se é o mesmo que o arquivo original.open(... O_CREAT|O_EXCL)
. Você só precisa de um programa de usuário adequado, comolockfile-create
(inlockfile-progs
) oudotlockfile
(inliblockfile-bin
). E certifique-se de limpar corretamente (por exemplotrap EXIT
) ou testar bloqueios obsoletos (por exemplo, com--use-pid
).Há um invólucro ao redor da chamada do sistema flock (2) chamado, sem imaginação, flock (1). Isso torna relativamente fácil obter bloqueios exclusivos de maneira confiável, sem se preocupar com a limpeza etc. Existem exemplos na página do manual sobre como usá-lo em um script de shell.
fonte
flock()
chamada do sistema não é POSIX e não funciona para arquivos em montagens NFS.flock -x -n %lock file% -c "%command%"
para garantir que apenas uma instância esteja em execução.Você precisa de uma operação atômica, como o rebanho, caso contrário, isso acabará por falhar.
Mas o que fazer se o rebanho não estiver disponível. Bem, há mkdir. Essa é uma operação atômica também. Apenas um processo resultará em um mkdir bem-sucedido, todos os outros falharão.
Então o código é:
Você precisa cuidar dos bloqueios obsoletos após uma falha, seu script nunca será executado novamente.
fonte
sleep 10
antesrmdir
e tente cascatear novamente - nada "vazará".Para tornar o bloqueio confiável, você precisa de uma operação atômica. Muitas das propostas acima não são atômicas. O utilitário lockfile (1) proposto parece promissor, como mencionado na página de manual, que é "resistente ao NFS". Se o seu sistema operacional não suporta o arquivo de bloqueio (1) e sua solução precisa funcionar no NFS, você não tem muitas opções ....
O NFSv2 possui duas operações atômicas:
Com o NFSv3, a chamada de criação também é atômica.
As operações de diretório NÃO são atômicas nos NFSv2 e NFSv3 (consulte o livro 'NFS Illustrated' de Brent Callaghan, ISBN 0-201-32570-5; Brent é um veterano do NFS na Sun).
Sabendo disso, você pode implementar bloqueios de rotação para arquivos e diretórios (no shell, não no PHP):
dir dir atual:
bloquear um arquivo:
desbloquear dir atual (suposição, o processo em execução realmente adquiriu o bloqueio):
desbloquear um arquivo (suposição, o processo em execução realmente adquiriu o bloqueio):
Remover também não é atômico; portanto, primeiro renomeie (que é atômico) e depois remova.
Para as chamadas de link simbólico e renomeação, os dois nomes de arquivos devem residir no mesmo sistema de arquivos. Minha proposta: use apenas nomes de arquivos simples (sem caminhos) e coloque o arquivo e bloqueie no mesmo diretório.
fonte
lockfile
se disponível, ou fallback para essesymlink
método, se não.mv
,rm
), deverm -f
ser usado, e nãorm
no caso de dois processos P1, P2 estarem correndo? Por exemplo, P1 começa a desbloquear commv
, em seguida, P2 bloqueia e, em seguida, P2 desbloqueia (ambosmv
erm
), finalmente P1 tentarm
e falha.$$
no${f}.deleteme
nome do arquivo.Outra opção é usar a
noclobber
opção do shell executandoset -C
. Então>
falhará se o arquivo já existir.Em resumo:
Isso faz com que o shell chame:
que cria atomicamente o arquivo ou falha se o arquivo já existe.
De acordo com um comentário no BashFAQ 045 , isso pode falhar
ksh88
, mas funciona em todas as minhas conchas:Interessante que
pdksh
adiciona aO_TRUNC
flag, mas obviamente é redundante:você está criando um arquivo vazio ou não está fazendo nada.
Como você faz isso
rm
depende de como você deseja que saídas impuras sejam manipuladas.Excluir na saída limpa
Novas execuções falham até que o problema que causou a saída impura seja resolvido e o arquivo de bloqueio seja removido manualmente.
Excluir em qualquer saída
Novas execuções são bem-sucedidas, desde que o script ainda não esteja em execução.
fonte
Você pode usar
GNU Parallel
isso, pois ele funciona como um mutex quando chamado comosem
. Portanto, em termos concretos, você pode usar:Se você deseja um tempo limite também, use:
Tempo limite de <0 significa sair sem executar o script se o semáforo não for liberado dentro do tempo limite, tempo limite de> 0 significa executar o script de qualquer maneira.
Observe que você deve dar um nome (com
--id
) ou o padrão é o terminal de controle.GNU Parallel
é uma instalação muito simples na maioria das plataformas Linux / OSX / Unix - é apenas um script Perl.fonte
sem
a questão relacionada unix.stackexchange.com/a/322200/199525 .Para scripts de shell, costumo usar o
mkdir
overflock
, pois torna os bloqueios mais portáteis.De qualquer maneira, usar
set -e
não é suficiente. Isso sai do script apenas se algum comando falhar. Seus bloqueios ainda serão deixados para trás.Para uma limpeza adequada dos bloqueios, você realmente deve definir seus traps para algo como este código psuedo (elevado, simplificado e não testado, mas com scripts usados ativamente):
Aqui está o que vai acontecer. Todas as armadilhas produzirão uma saída, para que a função
__sig_exit
sempre aconteça (com exceção de um SIGKILL), que limpa seus bloqueios.Nota: meus valores de saída não são baixos. Por quê? Vários sistemas de processamento em lote atendem ou têm expectativas dos números de 0 a 31. Configurando-os para outra coisa, posso fazer com que meus scripts e fluxos de lote reajam de acordo com o trabalho ou script em lote anterior.
fonte
rm -r $LOCK_DIR
ou até forçá-los conforme necessário (como fiz também em casos especiais, como manter arquivos de rascunho relativos). Felicidades.exit 1002
?Muito rápido e muito sujo? Esta linha única na parte superior do seu script funcionará:
Obviamente, apenas verifique se o nome do seu script é único. :)
fonte
-gt 2
? O grep nem sempre se encontra no resultado do ps!pgrep
não está no POSIX. Se você deseja que isso funcione de maneira portável, é necessário o POSIXps
e processar sua saída.-c
não existe, você terá que usar| wc -l
. Sobre a comparação de números:-gt 1
está marcado desde que a primeira instância se vê.Aqui está uma abordagem que combina o bloqueio de diretório atômico com uma verificação de bloqueio obsoleto via PID e reinicia se obsoleto. Além disso, isso não depende de nenhum basismo.
fonte
Criar um arquivo de bloqueio em um local conhecido e verificar a existência no início do script? Colocar o PID no arquivo pode ser útil se alguém tentar rastrear uma instância incorreta que esteja impedindo a execução do script.
fonte
Este exemplo é explicado no man rebanho, mas precisa de alguns aprimoramentos, porque devemos gerenciar bugs e códigos de saída:
Você pode usar outro método, listar processos que eu usei no passado. Mas isso é mais complicado que o método acima. Você deve listar os processos por ps, filtrar por seu nome, filtro adicional grep -v grep para remover o parasita e, finalmente, contá-lo por grep -c. e compare com o número. É complicado e incerto
fonte
As respostas existentes publicadas dependem do utilitário CLI
flock
ou não protegem adequadamente o arquivo de bloqueio. O utilitário flock não está disponível em todos os sistemas não Linux (por exemplo, FreeBSD) e não funciona corretamente no NFS.Nos meus primeiros dias de administração e desenvolvimento do sistema, me disseram que um método seguro e relativamente portátil de criar um arquivo de bloqueio era criar um arquivo temporário usando
mkemp(3)
oumkemp(1)
, gravando informações de identificação no arquivo temporário (ou seja, PID) e, em seguida, vinculando o hardware o arquivo temporário para o arquivo de bloqueio. Se o link foi bem-sucedido, você obteve o bloqueio com êxito.Ao usar bloqueios em scripts de shell, normalmente coloco uma
obtain_lock()
função em um perfil compartilhado e depois a origino dos scripts. Abaixo está um exemplo da minha função de bloqueio:A seguir, é apresentado um exemplo de como usar a função de bloqueio:
Lembre-se de ligar
clean_up
em qualquer ponto de saída do seu script.Eu usei o acima em ambos os ambientes Linux e FreeBSD.
fonte
Ao direcionar uma máquina Debian, acho o
lockfile-progs
pacote uma boa solução.procmail
também vem com umalockfile
ferramenta. No entanto, às vezes eu estou preso com nenhum deles.Aqui está minha solução que usa
mkdir
atômica e um arquivo PID para detectar bloqueios obsoletos. Atualmente, esse código está em produção em uma configuração do Cygwin e funciona bem.Para usá-lo, basta ligar
exclusive_lock_require
quando precisar obter acesso exclusivo a algo. Um parâmetro opcional de nome de bloqueio permite compartilhar bloqueios entre diferentes scripts. Há também duas funções de nível inferior (exclusive_lock_try
eexclusive_lock_retry
), caso você precise de algo mais complexo.fonte
Se as limitações do rebanho, que já foram descritas em outras partes deste segmento, não são um problema para você, isso deve funcionar:
fonte
-n
iráexit 1
imediatamente se não puder obter o bloqueioAlguns unixes possuem
lockfile
muito semelhante ao já mencionadoflock
.Na página de manual:
fonte
lockfile
utilitário?lockfile
é distribuído comprocmail
. Também há uma alternativadotlockfile
que acompanha oliblockfile
pacote. Ambos afirmam trabalhar de maneira confiável no NFS.Na verdade, embora a resposta do bmdhacks seja quase boa, há uma pequena chance de o segundo script ser executado depois de verificar primeiro o arquivo de bloqueio e antes de escrevê-lo. Então, ambos escreverão o arquivo de bloqueio e estarão executando. Aqui está como fazê-lo funcionar com certeza:
A
noclobber
opção garantirá que o comando de redirecionamento falhe se o arquivo já existir. Portanto, o comando de redirecionamento é realmente atômico - você escreve e verifica o arquivo com um comando. Você não precisa remover o arquivo de bloqueio no final do arquivo - ele será removido pela armadilha. Espero que isso ajude as pessoas que o lerão mais tarde.PS: Não vi que Mikel já respondesse a pergunta corretamente, embora ele não incluísse o comando trap para reduzir a chance de o arquivo de bloqueio ser deixado após a interrupção do script com Ctrl-C, por exemplo. Portanto, esta é a solução completa
fonte
Eu queria acabar com arquivos de bloqueio, lockdirs, programas especiais de bloqueio e, mesmo
pidof
que não sejam encontrados em todas as instalações do Linux. Também queria ter o código mais simples possível (ou pelo menos o menor número possível de linhas).if
Declaração mais simples , em uma linha:fonte
/bin/ps -a --format pid,cmd | awk -v pid=$$ '/'$(basename $0)'/ { if ($1!=pid) print $1; }'
Eu uso uma abordagem simples que lida com arquivos de bloqueio obsoletos.
Observe que algumas das soluções acima que armazenam o pid, ignoram o fato de que o pid pode ser contornado. Portanto, apenas verificar se existe um processo válido com o pid armazenado não é suficiente, especialmente para scripts de execução longa.
Eu uso o noclobber para garantir que apenas um script possa abrir e gravar no arquivo de bloqueio de uma vez. Além disso, armazeno informações suficientes para identificar exclusivamente um processo no arquivo de bloqueio. Defino o conjunto de dados para identificar exclusivamente um processo a ser pid, ppid, lstart.
Quando um novo script é iniciado, se não conseguir criar o arquivo de bloqueio, ele verifica se o processo que criou o arquivo de bloqueio ainda está por aí. Caso contrário, presumimos que o processo original tenha sofrido uma morte sem graça e deixamos um arquivo de bloqueio obsoleto. O novo script assume a propriedade do arquivo de bloqueio e tudo está bem no mundo novamente.
Deve funcionar com vários shells em várias plataformas. Rápido, portátil e simples.
fonte
Adicione esta linha no início do seu script
É um código padrão do man rebanho.
Se você quiser mais registros, use este
Isso define e verifica bloqueios usando
flock
utilitário. Esse código detecta se foi executado pela primeira vez verificando a variável FLOCKER, se não estiver definido como o nome do script, tenta iniciar o script novamente recursivamente usando flock e com a variável FLOCKER inicializada, se FLOCKER estiver definido corretamente, e em seguida na iteração anterior foi bem-sucedido e não há problema em prosseguir. Se o bloqueio estiver ocupado, ele falhará com o código de saída configurável.Parece não funcionar no Debian 7, mas parece funcionar novamente com o pacote experimental util-linux 2.25. Ele escreve "rebanho: ... Arquivo de texto ocupado". Isso pode ser substituído desativando a permissão de gravação no seu script.
fonte
PID e arquivos de bloqueio são definitivamente os mais confiáveis. Quando você tenta executar o programa, ele pode verificar o arquivo de bloqueio que, se existir, pode ser usado
ps
para verificar se o processo ainda está em execução. Caso contrário, o script pode iniciar, atualizando o PID no arquivo de bloqueio para seu próprio.fonte
Acho que a solução do bmdhack é a mais prática, pelo menos para o meu caso de uso. O uso de flock e lockfile depende da remoção do arquivo de bloqueio usando rm quando o script termina, o que nem sempre pode ser garantido (por exemplo, kill -9).
Eu mudaria um pouco da solução do bmdhack: faz questão de remover o arquivo de bloqueio, sem afirmar que isso é desnecessário para o trabalho seguro desse semáforo. Seu uso de kill -0 garante que um arquivo de bloqueio antigo para um processo morto seja simplesmente ignorado / sobrescrito.
Minha solução simplificada é, portanto, simplesmente adicionar o seguinte ao topo do seu singleton:
Obviamente, esse script ainda tem a falha de que os processos que provavelmente iniciarão ao mesmo tempo apresentam um risco de corrida, pois o teste de bloqueio e as operações de conjunto não são uma ação atômica única. Mas a solução proposta pelo lhunath para usar o mkdir tem a falha que um script morto pode deixar para trás do diretório, impedindo a execução de outras instâncias.
fonte
O utilitário semáforo usa
flock
(como discutido acima, por exemplo, presto8) para implementar um semáforo de contagem . Ele permite qualquer número específico de processos simultâneos que você deseja. Nós o usamos para limitar o nível de simultaneidade de vários processos do operador de fila.É como sem, mas muito mais leve. (Divulgação completa: escrevi depois de descobrir que o sem era muito pesado para nossas necessidades e não havia um utilitário simples de semáforo de contagem disponível.)
fonte
Um exemplo com o flock (1), mas sem subshell. O arquivo flock () ed / tmp / foo nunca é removido, mas isso não importa, pois ele é flock () e un-flock () ed.
fonte
Já respondeu um milhão de vezes, mas de outra maneira, sem a necessidade de dependências externas:
Cada vez que ele grava o PID atual ($$) no arquivo de bloqueio e na inicialização do script, verifica se um processo está sendo executado com o PID mais recente.
fonte
Usar o bloqueio do processo é muito mais forte e também cuida das saídas desagradáveis. lock_file é mantido aberto enquanto o processo estiver em execução. Ele será fechado (pelo shell) assim que o processo existir (mesmo que seja morto). Eu achei isso muito eficiente:
fonte
Eu uso oneliner @ no início do script:
É bom ver a presença de processo na memória (não importa qual seja o status do processo); mas faz o trabalho para mim.
fonte
O caminho do rebanho é o caminho a percorrer. Pense no que acontece quando o script morre repentinamente. No caso do rebanho, você apenas perde o rebanho, mas isso não é um problema. Além disso, observe que um truque maléfico é atacar o próprio script ... mas isso obviamente permite que você corra a todo vapor em problemas de permissão.
fonte
Rapido e sujo?
fonte