Kill programa depois que ele gera uma determinada linha, a partir de um shell script

15

Fundo:

Estou escrevendo um script de teste para um software de biologia computacional. O software que estou testando pode levar dias ou até semanas para ser executado, por isso possui uma funcionalidade de recuperação integrada, no caso de falhas no sistema ou falhas de energia.

Estou tentando descobrir como testar o sistema de recuperação. Especificamente, não consigo descobrir uma maneira de "travar" o programa de maneira controlada. Eu estava pensando em, de alguma forma, cronometrar uma instrução SIGKILL para executar após algum tempo. Provavelmente, isso não é o ideal, pois não é garantido que o caso de teste execute a mesma velocidade todas as vezes (ele é executado em um ambiente compartilhado); portanto, seria difícil comparar os logs com a saída desejada.

Este software imprime uma linha para cada seção de análise concluída.

Questão:

Eu queria saber se havia uma maneira boa / elegante (em um script de shell) para capturar a saída de um programa e depois matar o programa quando uma determinada linha / # de linhas é produzida pelo programa?

Paulo
fonte
1
Por que você precisa interromper o programa em um horário específico? Devido ao buffer de saída, você pode acabar matando o programa por um longo tempo após a saída do texto que você está procurando.
stardt
@stardt Eu queria interromper o programa em um horário específico, para que os resultados dos testes fossem mais controlados e reproduzíveis. Eu encontrei uma maneira de contornar isso agora. Eu matarei o programa apenas uma vez manualmente e testarei apenas o código de recuperação. Todas as tentativas de recuperação serão iniciadas a partir do mesmo ponto. Estou testando como ele se recupera, e não como ele trava depois de tudo :)
Paul
1
Possível duplicado de "relógio" da saída de um comando até que uma determinada seqüência é observado e depois sair
Ciro Santilli新疆改造中心法轮功六四事件

Respostas:

7

Um script de wrapper como este é uma abordagem padrão. O script executa o programa em segundo plano e depois faz um loop, verificando o arquivo de log a cada minuto em busca de alguma sequência. Se a string for encontrada, o programa em segundo plano será eliminado e o script será encerrado.

command="prog -foo -whatever"
log="prog.log"
match="this is the what i want to match"

$command > "$log" 2>&1 &
pid=$!

while sleep 60
do
    if fgrep --quiet "$match" "$log"
    then
        kill $pid
        exit 0
    fi
done
Kyle Jones
fonte
14

Se o seu programa for finalizado no SIGPIPE (que é a ação padrão), deve ser suficiente direcionar a saída para um leitor que sairá ao ler essa linha.

Portanto, pode ser tão simples quanto

$ program | sed -e '/Suitable text from the line/q'

Se você deseja suprimir a saída padrão, use

$ program | sed -n -e '/Suitable text from the line/q'

Da mesma forma, se alguém quiser parar após um certo número de linhas, poderá usar a cabeça no lugar de sed, por exemplo

$ program | head -n$NUMBER_OF_LINES_TO_STOP_AFTER

O tempo exato de pelo que a matança ocorre faz depender do comportamento de buffer do terminal como stardt sugere nos comentários.

dmckee --- gatinho ex-moderador
fonte
-1. Há uma falha séria aqui. O programa será encerrado apenas quando (se) tentar gravar no canal (quebrado) após o sedtérmino. O OP diz "Este software imprime uma linha para cada seção de análise concluída". Isso pode significar que o programa completará uma seção extra! Prova de conceito: { echo 1; sleep 3; >&2 echo peekaboo; sleep 3; echo 2; } | sed -e '/1/q'. Veja esta resposta . Solução possível: ( program & ) | sed -e '/Suitable text from the line/q'; killall program. Faça sua resposta ciente da falha e eu revogarei meu voto negativo.
Kamil Maciorowski
Hmmm ... de fato. E a questão do buffer torna-o não confiável, mesmo com a sua melhoria (embora, é claro, isso afete todas as soluções que vejo aqui). Eu gosto de fazer uso da infraestrutura de sinal sempre que possível, mas em algum momento começa a perder a elegância. Talvez seja melhor apenas desfazer a coisa toda.
dmckee --- gatinho ex-moderador
7

Como alternativa à resposta do dmckee, o grepcomando com a -mopção (consulte, por exemplo, esta página de manual ) também pode ser usado:

compbio | grep -m 1 "Text to match"

para parar quando uma linha correspondente ao texto for encontrada ou

compbio | grep -v -m 10 "Text to match"

aguardar 10 linhas que não correspondem ao texto fornecido.

stardt
fonte
3

Você pode usar expect(1)para assistir ao stdout de um programa e executar ações predefinidas em alguns padrões.

#!/usr/bin/env expect

spawn your-program -foo -bar 
expect "I want this text" { close }

Ou um one-liner para incorporar em outro script:

expect -c "spawn your-program -foo -bar; expect \"I want this text\" { close }"

Observe que o expectcomando não vem com todos os sistemas operacionais por padrão. Pode ser necessário instalá-lo.

Jamesits
fonte
Esta é uma boa solução. Eu recomendo mencionar set timeout -1para definir o tempo limite indefinido, caso contrário, tudo será fechado com expecto tempo limite padrão de 10s.
pepper_chico 24/09/19
1

Você pode usar uma declaração "while":

Você precisa canalizar a saída do seu software (ou de seus logs) e, em seguida, para cada nova linha, fazer um teste de condição e executar kill -KILL. Por exemplo (eu testei com / var / log / messages como o arquivo de log):

tail -f /var/log/messages | while read line; do test "$line" = "THE LINE YOU WANT TO MATCH" && kill PIDTOKILL; done

Ou com o software diretamente:

./biosoftware | while read line; do test "$line" = "THE LINE YOU WANT TO MATCH" && kill PIDTOKILL; done

Você precisa substituir o caminho do log / nome do aplicativo, a linha a ser correspondida e o PID a ser eliminado (você também pode usar o killall).

Boa sorte com seu software,

Hugo,

pistache
fonte