Matando um script de shell em execução em segundo plano

12

Eu escrevi um script de shell para monitorar um diretório usando o utilitário inotifywait do inotifyt-tools. Quero que esse script seja executado continuamente em segundo plano, mas também quero poder interrompê-lo quando desejado.

Para fazê-lo funcionar continuamente, eu usei while true; como isso:

while true;
do #a set of commands that use the inotifywait utility
end

Salvei-o em um arquivo /bine o tornei executável. Para executá-lo em segundo plano, usei nohup <script-name> &e fechei o terminal.

Não sei como paro esse script. Eu olhei para as respostas aqui e uma pergunta muito relacionada aqui .

ATUALIZAÇÃO 1: Com base na resposta de @InfectedRoot abaixo, consegui resolver meu problema usando a seguinte estratégia. Primeiro uso

ps -aux | grep script_name

e use sudo kill -9 <pid>para matar os processos. Eu tive que pgrep inotifywaitusar sudo kill -9 <pid>novamente para o ID retornado.

Isso funciona, mas acho que é uma abordagem confusa, estou procurando uma resposta melhor.

ATUALIZAÇÃO 2: A resposta consiste em matar 2 processos . Isso é importante porque a execução do script na linha de comando inicia 2 processos, 1 o próprio script e 2, o processo de inotificação .

light94
fonte
2
Remover a -9opção kille usá-la apenas killeliminará a bagunça.
Sree
1
Bravo. Para colocar alguns '$$' na caixa de trocadilhos novamente, eu gostaria de enfatizar que a -9opção seria total killaqui. : P
syntaxerror 15/12
Por uma questão de completude: existe uma abordagem limpa para matar os dois processos de uma só vez: em vez de matar os processos separadamente, você pode matar o grupo de processos do script para matá-los de uma só vez. O PGID é o mesmo que o pid do processo principal shell script e killele você prefixar o PGID com um sinal de menos: kill -- -<pgid>, kill -9 -<pgid>, etc.
cg909

Respostas:

6

Para melhorar, usar killalle também combinar os comandos:

ps -aux | grep script_name
killall script_name inotifywait

Ou faça tudo em uma linha:

killall `ps -aux | grep script_name | grep -v grep | awk '{ print $1 }'` && killall inotifywait
cremefraiche
fonte
Sua solução acima funciona, mas não o comando de uma linha. Por favor, você pode conferir. Eu recebo um erro: nenhum processo
light94
@ light94 Error: no processdeve significar que você já o matou. Você pode testá-lo abrindo outros dois programas, como o VLC & Geany , e testando com eles.
Cremefraiche
Ei @cremefraiche, como earler disse que seu código funciona .. mas eu tenho procurado soluções alternativas e a solução mais comum que vejo é usar kill <pid>. Como meu script não termina dessa maneira, estou um pouco preocupado se meu código é impróprio? Você pode me guiar, por favor?
precisa
Você pode economizar ao se grep -v greptransformar grep script_nameem grep -e "[s]cript_name".
nmichaels
3

Listar os trabalhos em segundo plano usando

# jobs

Em seguida, escolha o número anterior de trabalho e execute

exemplo

# fg 1 

insira a descrição da imagem aqui

trará para o primeiro plano.

Em seguida, mate-o usando CTRL + C ou a maneira mais fácil de encontrar o PID do script usando

ps -aux | grep script_name

insira a descrição da imagem aqui

Então mate usando pid

sudo kill -9 pid_number_here
Babin Lonston
fonte
2
A primeira opção não é válida para o meu caso, porque eu fecho o shell depois de declarar o script e acho que o comando jobs funciona apenas para o mesmo shell. Além disso, tentei a segunda opção e isso mata o processo, mas quando eu faço o pgrep inotifywait, ainda posso vê-lo como um processo lá.
light94
jobs exibirá a lista de jobs, mesmo que você feche o shell. Ainda assim, o processo será concluído.
Babin Lonston
2
Eu tentei, mas não o fez.
light94
@ light94 Você está certo. Aqui acontecia frequentemente que o jobscomando realmente exibia apenas os trabalhos em execução enquanto estava no shell. Depois que fechei uma determinada janela do terminal, a única maneira de acessá-los era via ps(veja acima para isso). Portanto, é certo que você não está inventando isso.
Syntaxerror 15/12/14
2

Você pode usar ps+ grepou pgreppara obter o nome do processo / pid ; depois use killall/ pkillpara matar o nome do processo ou use killpara matar o pid. Todos os seguintes devem funcionar.

killall $(ps aux | grep script_name | grep -v grep | awk '{ print $1 }') && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $1 }' | xargs killall) && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $2 }' | xargs kill) && killall inotifywait
(pgrep -x script_name | xargs kill) && pkill -x inotifywait
pkill -x script_name && pkill -x inotifywait

O mais importante é garantir que você mate apenas o (s) processo (s) exato (s) que deseja matar.

pkill/ pgrepcorresponde a um padrão e não ao nome exato ; portanto, é mais perigoso; aqui -xé adicionado para corresponder ao nome exato.

Além disso, ao usar pgrep/ pkillvocê pode precisar

  • -fpara coincidir com a linha de comando completa (como ps auxfaz)
  • -a para imprimir também o nome do processo.
Hongxu Chen
fonte
Depois de executar o acima, recebo um erro de uso em cada um. Estou copiando e substituindo o scriptname. Eu recebo o uso de kill como saída
light94
O script_name está em execução? Funciona para mim (debian jessie)
Hongxu Chen
Sim, está em execução. e sua solução é quase a mesma das soluções @cremefraiche acima, então eu esperava que funcionasse da mesma maneira: /
light94
Sim, eu estava resumindo um pouco as possíveis maneiras de fazer isso, especialmente adicionando pgrep/ pkill. Se isso ainda der errado, você pode tentar pgrepsem -x. Basicamente, não acho que seja bom interromper o processo em um comando de linha sem verificar se outros processos são compatíveis.
Hongxu Chen
1

Como você provavelmente pode dizer, existem várias maneiras de fazer isso.

Em relação à sua "ATUALIZAÇÃO # 2" - em geral, encerrar qualquer processo em uma hierarquia pai-filho geralmente encerrará todos os processos associados. Mas há muitas exceções a isso. Idealmente, você deseja encerrar o 'filho' final em uma árvore de processos; os pais desse filho devem sair se não tiverem outras tarefas para executar. Mas se você matar um pai, o sinal deve ser retransmitido para os filhos quando o pai morrer e os filhos também devem sair - mas há casos em que os processos filhos podem ignorar o sinal (por meio de armadilhas ou mecanismos semelhantes) e podem continuar executar um será herdado pelo processo 'init' (ou similar). Mas esse assunto de comportamento do processo pode se tornar complexo e eu vou deixá-lo lá ...

Um método que eu gosto se não quiser usar um script de controle (descrito a seguir) é usar o utilitário 'screen' para iniciar e gerenciar o processo. O comando 'screen' está cheio de recursos e pode levar algum tempo para dominar. Recomendamos que você leia a página do manual 'screen' para obter uma explicação completa. Um exemplo rápido para iniciar um processo em segundo plano seria o comando:

tela -d -m / caminho / para / programa

Isso iniciará "/ path / to / program" dentro de uma sessão de 'tela'.

Você pode ver sua sessão em execução com o comando:

tela -ls

E a qualquer momento, você pode reconectar-se ao seu programa em execução com o comando:

tela -r

E então apenas termine com um ^ C ou o que seja.

Além da beleza de poder reconectar e desconectar do seu processo à vontade, essa 'tela' capturará qualquer stdout () que seu programa possa produzir.


Mas minha preferência pessoal nessas questões é ter um programa de controle que gerencia o início e a parada de um processo. Isso pode se tornar um pouco complicado e requer alguns scripts discutivelmente complicados. E, como qualquer script, existem dezenas de boas maneiras de fazê-lo. Incluí um exemplo básico de um método usado rotineiramente para iniciar e parar aplicativos. Se sua tarefa é simples, você pode inseri-la diretamente no script de controle - ou pode fazer com que esse script de controle chame outro programa externo. Observe que este exemplo não é de forma alguma abrangente em termos de gerenciamento do processo. Eu deixei de fora a possibilidade de cenários como: Garantir que o script ainda não esteja em execução quando você usar a opção "start", validando que o PID em execução é realmente o processo que você iniciou (por exemplo, seu script não ' morreu e outro processo foi iniciado usando o mesmo PID) e validando que o script realmente respondeu (saiu) na primeira solicitação de "interrupção". Fazer todas essas verificações pode ficar complicado e eu não queria tornar o exemplo muito longo e complexo. Você pode modificar o exemplo para praticar seu script de shell.

Salve o seguinte código em um arquivo chamado "programctl", torne-o executável com o comando:

chmod 755 programctl

Em seguida, edite o arquivo e adicione seu código / script na seção do caso que começa com "myscript".

Depois que tudo estiver no lugar, assumindo que "programctl" esteja no diretório atual, você poderá iniciar seu programa com:

./programctl start

E pare com:

./programctl stop

Felicidades.

#!/bin/bash
# Description:  A wrapper script used to stop/start another script.

#--------------------------------------
# Define Global Environment Settings:
#--------------------------------------

# Name and location of a persistent PID file

PIDFILE="/tmp/tmpfile-$LOGNAME.txt"

#--------------------------------------
# Check command line option and run...
# Note that "myscript" should not
# provided by the user.
#--------------------------------------

case $1
in
    myscript)
        # This is where your script would go.
        # If this is a routine 'bash' shell script, you can enter
        # the script below as illustrated in the example.  
        # Or you could simply provide the path and parameters
        # to another script such as /dir/name/command -options

        # Example of an embedded script:

        while true
        do
            # do something over and over...
            sleep 1
        done

        # Example of an external script:

        /usr/local/bin/longrun -x
    ;;

    start)
        # Start your script in the background.
        # (Note that this is a recursive call to the wrapper
        #  itself that effectively runs your script located above.)
        $0 myscript &

        # Save the backgound job process number into a file.
        jobs -p > $PIDFILE

        # Disconnect the job from this shell.
        # (Note that 'disown' command is only in the 'bash' shell.)
        disown %1

        # Print a message indicating the script has been started
        echo "Script has been started..."
    ;;

    stop)
        # Read the process number into the variable called PID
        read PID < $PIDFILE

        # Remove the PIDFILE
        rm -f $PIDFILE

        # Send a 'terminate' signal to process
        kill $PID

        # Print a message indicating the script has been stopped
        echo "Script has been stopped..."
    ;;

    *)
        # Print a "usage" message in case no arguments are supplied
        echo "Usage: $0 start | stop"
    ;;
esac
Christopher Morris
fonte
Eu tentei o método "tela" que você sugeriu e funciona lindamente. Ainda estou para tentar o segundo método. Obrigado pela resposta.
light94