Aguarde a conclusão de um processo

147

Existe algum recurso interno no Bash para aguardar a conclusão de um processo?

O waitcomando permite apenas aguardar a conclusão dos processos filhos. Gostaria de saber se há alguma maneira de aguardar a conclusão de qualquer processo antes de prosseguir em qualquer script.

Uma maneira mecânica de fazer isso é a seguinte, mas eu gostaria de saber se existe algum recurso interno no Bash.

while ps -p `cat $PID_FILE` > /dev/null; do sleep 1; done
Biswajyoti Das
fonte
4
Deixe-me dar duas precauções : 1. Como indicado abaixo pelo mp3foley , "kill -0" nem sempre funciona para o POSIX. 2. Provavelmente você também quer ter certeza de que o processo não é um zumbi, que é praticamente um processo encerrado. Veja o comentário do mp3foley e o meu para obter detalhes.
Teika kazura
2
Outro cuidado ( originalmente indicado por ks1322 abaixo): Usar PID diferente de um processo filho não é robusto. Se você deseja uma maneira segura, use, por exemplo, IPC.
Teika kazura

Respostas:

138

Para aguardar a conclusão de qualquer processo

Linux:

tail --pid=$pid -f /dev/null

Darwin (requer que $pidtenha arquivos abertos):

lsof -p $pid +r 1 &>/dev/null

Com tempo limite (segundos)

Linux:

timeout $timeout tail --pid=$pid -f /dev/null

Darwin (requer que $pidtenha arquivos abertos):

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)
Rauno Palosaari
fonte
42
Quem saberia que tailfaria isso.
ctrl-alt-Delor
8
tailtrabalha sob o capô pesquisando kill(pid, SIG_0)um processo (descoberto usando strace).
Att Righ
2
Observe que lsof usa sondagem, ou +r 1seja, o tempo limite, estou procurando pessoalmente uma solução para o MacOS que não use sondagem.
Alexander Mills
1
Este truque falha para zumbis. Tudo bem para processos que você não pode matar; tailtem a linha kill (pid, 0) != 0 && errno != EPERM.
Teika Kazura
2
O @AlexanderMills, se você puder tolerar que seu sistema macOS não durma enquanto o comando é executado, caffeinate -w $pidfará o truque.
Zneak 20/09/19
83

Não há embutido. Use kill -0em loop para uma solução viável:

anywait(){

    for pid in "$@"; do
        while kill -0 "$pid"; do
            sleep 0.5
        done
    done
}

Ou como um oneliner mais simples para facilitar o uso único:

while kill -0 PIDS 2> /dev/null; do sleep 1; done;

Conforme observado por vários comentaristas, se você deseja aguardar processos para os quais não tem o privilégio de enviar sinais, há outra maneira de detectar se o processo está sendo executado para substituir a kill -0 $pidchamada. No Linux, test -d "/proc/$pid"funciona, em outros sistemas que você pode precisar usar pgrep(se disponível) ou algo parecido ps | grep "^$pid ".

Urso de pelúcia
fonte
2
Cuidado : Isso nem sempre funciona, conforme indicado abaixo em mp3foley . Veja esse comentário e o meu para obter detalhes.
Teika Kazura
2
Cuidado 2 (Em zumbis): O comentário de Teddy acima ainda não é suficiente, pois podem ser zumbis. Veja minha resposta abaixo para uma solução Linux.
Teika kazura
4
Esta solução não arrisca uma condição de corrida? Enquanto dorme sleep 0.5, o processo com $pidpode morrer e outro processo pode ser criado com o mesmo $pid. E vamos acabar esperando por 2 processos diferentes (ou até mais) com o mesmo $pid.
precisa saber é o seguinte
2
@ ks1322 Sim, esse código realmente possui uma condição de corrida.
Teddy
4
Os PIDs geralmente não são gerados sequencialmente? Qual é a probabilidade da contagem em um segundo?
Esmiralha 28/04
53

Eu descobri que "kill -0" não funciona se o processo é de propriedade do root (ou outro), então usei o pgrep e criei:

while pgrep -u root process_name > /dev/null; do sleep 1; done

Isso teria a desvantagem de provavelmente corresponder aos processos zumbis.

mp3foley
fonte
2
Boa observação. No POSIX, a chamada do sistema kill(pid, sig=0)falha se o processo do responsável pela chamada não tiver um privilégio de matar. Assim, / bin / kill -0 e "kill -0" (bash embutido) também falham na mesma condição.
Teika kazura
31

Esse loop do script bash termina se o processo não existir ou se for um zumbi.

PID=<pid to watch>
while s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do
    sleep 1
done

EDIT : O script acima foi dado abaixo pela Rockallite . Obrigado!

Minha resposta original abaixo funciona para Linux, contando com procfsie /proc/. Não conheço sua portabilidade:

while [[ ( -d /proc/$PID ) && ( -z `grep zombie /proc/$PID/status` ) ]]; do
    sleep 1
done

Não se limita ao shell, mas os SOs não têm chamadas de sistema para assistir ao encerramento de processos não-filhos.

teika kazura
fonte
1
Agradável. Embora eu tinha para cercar grep /proc/$PID/statuscom aspas ( bash: test: argument expected)
Griddo
Hum ... tentei novamente e funcionou. Acho que fiz algo errado da última vez.
Griddo
7
Ouwhile s=`ps -p $PID -o s=` && [[ "$s" && "$s" != 'Z' ]]; do sleep 1; done
Rockallite
1
Infelizmente, isso não funciona no BusyBox - em sua ps, nem -pou s=são suportados
ZimbiX
14

O FreeBSD e o Solaris possuem esse pwait(1)utilitário útil , que faz exatamente o que você deseja.

Acredito que outros sistemas operacionais modernos também tenham as chamadas de sistema necessárias (o MacOS, por exemplo, implementa o BSD kqueue), mas nem todos o disponibilizam na linha de comando.

Mikhail T.
fonte
2
> BSD and Solaris: Inspecionando os três grandes BSDs que vêm à mente; nem o OpenBSD nem o NetBSD têm essa função (em suas páginas de manual), apenas o FreeBSD possui, como você pode verificar facilmente em man.openbsd.org .
precisa saber é
Parece que você está certo. Mea culpa ... Todos eles implementam kqueue, então compilar o FreeBSD pwait(1)seria trivial, no entanto. Por que os outros do BSD não iria importar o recurso me escapa ...
Mikhail T.
1
plink me@oracle box -pw redacted "pwait 6998";email -b -s "It's done" etcapenas me permitiu ir para casa agora, em vez de horas a partir de agora.
Zzxyz 17/08/19
11

Na página de manual do bash

   wait [n ...]
          Wait for each specified process and return its termination  status
          Each  n  may be a process ID or a job specification; if a
          job spec is given, all processes  in  that  job's  pipeline  are
          waited  for.  If n is not given, all currently active child processes
          are waited for, and the return  status  is  zero.   If  n
          specifies  a  non-existent  process or job, the return status is
          127.  Otherwise, the return status is the  exit  status  of  the
          last process or job waited for.
Charlweed
fonte
56
Isso é verdade, mas só pode esperar pelo filho do shell atual. Você não pode esperar por nenhum processo.
gumik
@ gumik: "Se n não for fornecido, todos os processos filhos atualmente ativos serão aguardados" . Isso funciona perfeitamente. waitSem args, o processo será bloqueado até que os processos filhos sejam concluídos. Honestamente, não vejo motivo para esperar por nenhum processo, pois sempre há processos do sistema em andamento.
Codigoofsalvation
1
@coderofsalvation (espera 10 e espera 3 e espera) leva 10 segundos para retornar: a espera sem argumentos bloqueará até que TODOS os processos filhos sejam concluídos. O OP quer ser notificado quando o primeiro processo filho (ou nomeado) terminar.
Android.weasel
Também não funciona se o processo não estiver em segundo plano ou em primeiro plano (no Solaris, Linux ou Cygwin). ex. sleep 1000 ctrl-z wait [sleep pid]retorna imediatamente
zzxyz 17/08/19
6

Todas essas soluções são testadas no Ubuntu 14.04:

Solução 1 (usando o comando ps): Apenas para adicionar a resposta de Pierz, sugiro:

while ps axg | grep -vw grep | grep -w process_name > /dev/null; do sleep 1; done

Nesse caso, grep -vw grepgarante que o grep corresponda apenas ao process_name e não ao próprio grep. Tem a vantagem de suportar os casos em que o process_name não está no final de uma linha em ps axg.

Solução 2 (usando o comando top e o nome do processo):

while [[ $(awk '$12=="process_name" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Substitua process_namepelo nome do processo que aparece em top -n 1 -b. Por favor, mantenha as aspas.

Para ver a lista de processos que você espera que sejam concluídos, você pode executar:

while : ; do p=$(awk '$12=="process_name" {print $0}' <(top -n 1 -b)); [[ $b ]] || break; echo $p; sleep 1; done

Solução 3 (usando o comando superior e o ID do processo):

while [[ $(awk '$1=="process_id" {print $0}' <(top -n 1 -b)) ]]; do sleep 1; done

Substitua process_idpelo ID do processo do seu programa.

Saeid BK
fonte
4
Voto negativo: o longo grep -v greppipeline é um antipadrão massivo, e isso pressupõe que você não tenha processos não relacionados com o mesmo nome. Se você conhece os PIDs, isso pode ser adaptado a uma solução que funcione corretamente.
Tripleee
Obrigado triplicado pelo comentário. Eu adicionei a bandeira -wpara evitar o problema de grep -v grep alguma extensão . Também adicionei mais duas soluções com base no seu comentário.
Saeid BK
5

Ok, parece que a resposta é - não, não existe uma ferramenta integrada.

Depois de definir /proc/sys/kernel/yama/ptrace_scopepara 0, é possível usar o straceprograma. Outros comutadores podem ser usados ​​para silenciá-lo, para que ele realmente espere passivamente:

strace -qqe '' -p <PID>
emu
fonte
1
Agradável! Parece, porém, que não é possível conectar-se a um determinado PID de dois lugares diferentes (eu entendo Operation not permitteda segunda instância de strace); você pode confirmar isso?
precisa saber é o seguinte
@eudoxos Sim, a página de manual do ptrace diz: (...)"tracee" always means "(one) thread"(e confirmo o erro que você mencionou). Para permitir que mais processos esperem dessa maneira, você teria que fazer uma cadeia.
emu
2

Solução de bloqueio

Use o waitem um loop, para aguardar o término de todos os processos:

function anywait()
{

    for pid in "$@"
    do
        wait $pid
        echo "Process $pid terminated"
    done
    echo 'All processes terminated'
}

Esta função sai imediatamente, quando todos os processos foram finalizados. Esta é a solução mais eficiente.

Solução sem bloqueio

Use o kill -0loop, para aguardar o término de todos os processos + faça qualquer coisa entre as verificações:

function anywait_w_status()
{
    for pid in "$@"
    do
        while kill -0 "$pid"
        do
            echo "Process $pid still running..."
            sleep 1
        done
    done
    echo 'All processes terminated'
}

O tempo de reação diminuiu para o sleeptempo, porque é necessário impedir o alto uso da CPU.

Um uso realista:

Aguardando o término de todos os processos + informe o usuário sobre todos os PIDs em execução.

function anywait_w_status2()
{
    while true
    do
        alive_pids=()
        for pid in "$@"
        do
            kill -0 "$pid" 2>/dev/null \
                && alive_pids+="$pid "
        done

        if [ ${#alive_pids[@]} -eq 0 ]
        then
            break
        fi

        echo "Process(es) still running... ${alive_pids[@]}"
        sleep 1
    done
    echo 'All processes terminated'
}

Notas

Essas funções obtêm PIDs por meio de argumentos $@como array BASH.

andras.tim
fonte
2

Teve o mesmo problema, resolvi o problema encerrando o processo e aguardando a conclusão de cada processo usando o sistema de arquivos PROC:

while [ -e /proc/${pid} ]; do sleep 0.1; done
Littlebridge
fonte
polling é muito ruim, você poderia ter uma visita da polícia :)
Alexander Mills
2

Não há recurso interno para aguardar a conclusão de qualquer processo.

Você pode enviar kill -0para qualquer PID encontrado, para não ficar intrigado com zumbis e outras coisas que ainda estarão visíveis ps(enquanto ainda recupera a lista de PIDs ps).

TheBonsai
fonte
1

Use inotifywait para monitorar algum arquivo que é fechado quando o processo terminar. Exemplo (no Linux):

yourproc >logfile.log & disown
inotifywait -q -e close logfile.log

-e especifica o evento a aguardar, -q significa saída mínima apenas na finalização. Nesse caso, será:

logfile.log CLOSE_WRITE,CLOSE

Um único comando de espera pode ser usado para aguardar vários processos:

yourproc1 >logfile1.log & disown
yourproc2 >logfile2.log & disown
yourproc3 >logfile3.log & disown
inotifywait -q -e close logfile1.log logfile2.log logfile3.log

A sequência de saída do inotifywait informará qual processo foi finalizado. Isso funciona apenas com arquivos 'reais', não com algo em / proc /

Burghard Hoffmann
fonte
0

Em um sistema como o OSX, você pode não ter o pgrep para tentar esta abordagem ao procurar processos pelo nome:

while ps axg | grep process_name$ > /dev/null; do sleep 1; done

O $símbolo no final do nome do processo garante que o grep corresponda apenas ao process_name ao final da linha na saída ps e não a si próprio.

Pierz
fonte
Horrível: Pode haver vários processos com esse nome em algum lugar da linha de comando , incluindo o seu próprio grep. Em vez de redirecionar para /dev/null, -qdeve ser usado com grep. Outra instância do processo pode ter começado enquanto o seu ciclo estava dormindo e você nunca saberá ...
Mikhail T.
Não sei por que você escolheu esta resposta como 'Horrível' - como uma abordagem semelhante foi sugerida por outras pessoas? Embora a -qsugestão seja válida, como mencionei na minha resposta, especificamente a terminação $significa que o grep não corresponderá ao nome "em algum lugar na linha de comando" nem corresponderá a si próprio. Você realmente tentou no OSX?
Pierz
0

A solução de Rauno Palosaari para Timeout in Seconds Darwin, é uma excelente solução alternativa para um sistema operacional semelhante ao UNIX que não possui GNU tail(não é específico a Darwin). Mas, dependendo da idade do sistema operacional do tipo UNIX, a linha de comando oferecida é mais complexa do que o necessário e pode falhar:

lsof -p $pid +r 1m%s -t | grep -qm1 $(date -v+${timeout}S +%s 2>/dev/null || echo INF)

Em pelo menos um UNIX antigo, o lsofargumento +r 1m%sfalha (mesmo para um superusuário):

lsof: can't read kernel name list.

O m%sé uma especificação de formato de saída. Um pós-processador mais simples não exige isso. Por exemplo, o seguinte comando aguarda no PID 5959 por até cinco segundos:

lsof -p 5959 +r 1 | awk '/^=/ { if (T++ >= 5) { exit 1 } }'

Neste exemplo, se o PID 5959 sair por conta própria antes dos cinco segundos decorridos, ${?}será 0. Se não ${?}retornar 1após cinco segundos.

Pode-se notar expressamente que em +r 1, o 1é o intervalo da pesquisa (em segundos), portanto, ele pode ser alterado para se adequar à situação.

Kbulgrien
fonte