Como garantir que apenas uma instância de um script bash seja executada?

21

Uma solução que não requer ferramentas adicionais seria preferida.

    
por Tobias Kienzler 18.09.2012 / 13:04

10 respostas

16

Quase como a resposta do nsg: use um diretório de bloqueio . A criação de diretórios é atômica em 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 você pode verificar esse PID para ver se o processo de bloqueio ainda é executado. Muitas condições de corrida se encontram nesse caminho.

    
por 18.09.2012 / 14:00
17

Para adicionar a resposta de Bruce Ediger e inspirada por esta resposta , você também deve adicionar mais smarts à limpeza para proteger contra a finalização do 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
    
por 20.01.2015 / 12:30
5

Isso pode ser simplista demais, por favor me corrija se eu estiver errado. Não é um simples ps suficiente?

#!/bin/bash 

me="$(basename "$0")";
running=$(ps h -C "$me" | grep -wv $$ | wc -l);
[[ $running > 1 ]] && exit;

# do stuff below this comment
    
por 18.09.2012 / 14:19
4

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
    
por 18.09.2012 / 13:18
3

Se você quiser ter certeza de que apenas uma instância do seu script esteja em execução, dê uma olhada em:

Bloqueie seu script (em 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, pensei em fazer 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 sendo executado, mesmo que você execute várias instâncias de <your_script> simultaneamente.

    
por 18.09.2012 / 13:17
2

Uma 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 já estiver em execução ou sair com o código de erro 1 se nenhum outro script estiver sendo executado

    
por 17.10.2017 / 22:25
1

Embora você tenha solicitado uma solução sem ferramentas adicionais, essa é a minha maneira favorita de usar flock :

#!/bin/sh

[ "${FLOCKER}" != "$0" ] && exec env FLOCKER="$0" flock "$0" "$0" "$@" || exit 1

echo "servus!"
sleep 10

Isso vem de man flock , embora eu tenha removido -n dos argumentos para flock , o que faz com que flock falhe em vez de esperar até que outras instâncias sejam concluídas. Você também pode usar um tempo limite ( -w ).

O script chama a si mesmo por flock se a variável de ambiente FLOCKER não estiver definida. Nesta invocação "recursiva", a dita variável é definida e a linha não faz nada.

O exit 1 é uma rede de segurança, então o script termina sem sucesso se flock não foi encontrado. As versões do dash / bash que testei não continuam a execução após um exec malsucedido, mas man flock tem || : .

Pontos a considerar:

  • Requer flock , o script de exemplo termina com um erro se não for encontrado
  • Não precisa de arquivo extra de bloqueio
  • Pode não funcionar se o script estiver em NFS (consulte link )

Veja também link .

    
por 07.02.2017 / 21:08
0

Você pode usar isto: link

sudo pip install -U pidlock

pidlock -n sleepy_script -c 'sleep 10'
    
por 12.01.2018 / 19:32
0

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}

Baseado em man flock , melhorando apenas:

  • o nome do arquivo de bloqueio, com base no nome completo do script
  • a mensagem executing

Onde coloco aqui o sleep 10 , você pode colocar todo o script principal.

    
por 03.09.2018 / 22:38
0

Esta é uma versão modificada da Resposta do Anselmo . A idéia é criar um descritor de arquivo somente leitura usando o próprio script bash e usar 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 pegada 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 falha ou tem sucesso.

    
por 02.11.2018 / 06:32

Tags