Detectar se uma ferramenta já está em execução, mas APENAS para o usuário atual

8

Até agora eu costumava

pidof -o %PPID -x "my-tool"

Detectar o pid de uma instância eventualmente em execução da minha ferramenta.

Esta é a versão curta do arquivo my-tool, um script executável do bash

#!/bin/bash 

if pidof -o %PPID -x "my-tool"; then
   echo "Already running"
   exit 1
fi

 ... if not running go on 

Mas agora eu preciso permitir uma única instância por usuário e várias por máquina , para que possamos ter até 100 my-tool em execução no mesmo momento, mas apenas 1 por usuário.

Note que eu preciso de um teste para criar algo como um singleton. Se a ferramenta for iniciada e houver outra instância em execução, ela se fechará.

Em resumo: eu preciso que um script bash possa detectar se ele já está sendo executado para o usuário atual e, nesse caso, ele deve sair.

Como ?

realtebo
fonte
Você não deve usar pidof, existem ferramentas mentira melhorpgrep
cat

Respostas:

12

Use em pgrepvez disso:

pgrep -cxu $USER -f my-tool

As opções usadas são:

   -c, --count
          Suppress  normal  output; instead print a count of matching pro
          cesses.  When count does not match anything, e.g. returns  zero,
          the command will return non-zero value.
   -x, --exact
          Only match processes whose names (or command line if -f is spec
          ified) exactly match the pattern.
   -u, --euid euid,...
          Only match processes whose effective user ID is listed.   Either
          the numerical or symbolical value may be used.

Se você quiser usar isso em um script bash que verifique se ele já está em execução, você pode usá-lo $0. Isso se expande para o caminho do script atual (por exemplo /home/username/bin/foo.sh), mas precisamos apenas foo.sh. Para conseguir isso, nós podemos remover tudo até o último /usando da festança ferramentas de manipulação de corda : ${0##*/}. Isso significa que podemos fazer algo como:

## If there are more than 1 instances of the current script run
## by this user
if [[ $(pgrep -cxu "$USER" "${0##*/}") -gt 1 ]];
then
        echo "Script already running, exiting."
        exit
fi

Você também pode considerar o uso de arquivos de bloqueio para isso:

## If the lock file exists
if [ -e /tmp/$USER.foo.lock ]; then
    ## Check if the PID in the lockfile is a running instance
    ## of foo.sh to guard against crashed scripts
    if ps $(cat /tmp/$USER.foo.lock) | grep foo.sh >/dev/null; then
        echo "Script foo.sh is already running, exiting"
        exit
    else
        echo "Lockfile contains a stale PID, continuing"
        rm /tmp/$USER.foo.lock 
    fi
fi
## Create the lockfile by printing the script's PID into it
echo $$ > /tmp/$USER.foo.lock

## Rest of the script here

## At the end, delete the lockfile
rm /tmp/$USER.foo.lock
Terdon
fonte
Vamos continuar esta discussão no chat .
terdon 26/07/16
1
Em vez de touch /tmp/$USER.foo.lockusar o PID do processo, a echo $$ >/tmp/$USER.foo.locklógica poderia ser incluída para testar se esse processo realmente existe.
Monty Harder
@ MontyHarder de fato, sugestão muito boa. Resposta editada, obrigado.
terdon 27/07/16
@terdon, teve problemas com o celular, excluiu esse comentário no próximo segundo. Eu quis dizer em $(< pidfile)vez de $(cat pidfile).
Sdkks
7

Você pode usar pgreppara descobrir se um processo está sendo executado por um usuário específico e, em seguida, iniciar o processo se ainda não estiver sendo executado pelo usuário:

#!/bin/bash
if pgrep -u "$USER" my-tool &>/dev/null; then
    echo 'You already have my-tool running'
else
    /path/to/my_tool
fi

A variável de ambiente,, $USERserá expandida para o usuário conectado no momento, ou seja, o usuário executando o script. Como estamos interessados ​​apenas em my-toolexecutar ou não, basta usar o status de saída diretamente com a ifconstrução.

Use este script como invólucro para iniciar my-toole faça com que os usuários usem apenas isso ou renomeie-o como my-toole renomeie o original my-toolpara outra coisa (e altere o nome dentro do script também).

heemail
fonte
Não consigo criar um invólucro, uma longa história ... como posso excluir o PID do processo atual?
realtebo 26/07/16
@realtebo ummm..exclude o significado do PID?
heemayl
2

Experimente com este trecho:

#!/bin/bash
MyProcessName=$(ps -p $$ -o args=)
Mypid=$$
AllPids=$(pgrep -fu "$(whoami)" "$MyProcessName")
AllPids=$(tr "\n" ' ' <<<"$AllPids")
Pids=$(sed "s/$Mypid//" <<<"$AllPids")
echo "$$: Instances including itself: $AllPids"
echo "$$: Instances different from itself: $Pids"

É importante não escrever, pgrep|trporque isso entraria em um mesmo shell nomeado.

rexkogitans
fonte
2

Quebre o comando que você deseja executar em um lote para garantir que apenas uma cópia seja executada por vez. Use um lock_file armazenado na árvore de diretórios pessoais do usuário para que cada usuário tenha seu próprio arquivo de bloqueio. Por exemplo:

lockfile = "~/lockfile"
(
 if flock -n 200; then
    command
 else
    echo "Could not get lock $lock_file
 fi
) 200>$lock_file

'comando' pode ser qualquer comando ou script do bash.

man flock dá exemplos de uso

um convidado
fonte
Realmente, eu não sabia o flockcomando de todos
realtebo 9/08/16