Como obter o ID do processo em segundo plano?

375

Inicio um processo em segundo plano a partir do meu script de shell e gostaria de finalizar esse processo quando meu script terminar.

Como obter o PID desse processo no meu script de shell? Tanto quanto posso ver, a variável $!contém o PID do script atual, não o processo em segundo plano.

Volodymyr Bezuglyy
fonte
8
$! está correto. Tem certeza de que está iniciando o script no BG? amostra por favor.
pixelbeat 15/12/2009
7
Sim $! está correto. Eu estava errado.
Volodymyr Bezuglyy
11
$$ contém o script atual PID.
HUB

Respostas:

583

Você precisa salvar o PID do processo em segundo plano no momento em que o inicia:

foo &
FOO_PID=$!
# do other stuff
kill $FOO_PID

Você não pode usar o controle de tarefas, pois esse é um recurso interativo e vinculado a um terminal de controle. Um script não terá necessariamente um terminal conectado, portanto o controle do trabalho não estará necessariamente disponível.

camh
fonte
22
Desde $! retorna o último pid do processo em segundo plano. É possível que algo comece entre fooe $!, e nós recebemos algo que é pid ao invés de foo?
WiSaGaN
53
@WiSaGaN: Não. Não há nada entre essas linhas. Qualquer outra atividade no sistema não afetará isso. $! será expandido para o PID do último processo em segundo plano nesse shell .
Camh
8
... que mangueira você se houver foovários comandos de canal (por exemplo tail -f somefile.txt | grep sometext). Nesses casos, você obterá o PID do comando grep em $!vez do comando tail, se era o que estava procurando. Você precisará usar jobsou psou os gostos nesta instância.
precisa
11
@ JohnRix: Não necessariamente. Você receberá o pid de grep, mas se você matá-lo, a cauda receberá um SIGPIPE quando tentar gravar no pipe. Mas assim que você tenta entrar em qualquer controle / gerenciamento de processo complicado, o bash / shell se torna bastante doloroso.
Camh
5
Outra solução digna é sugerido em (um comentário a uma resposta a) Como se PID do processo apenas começou : oh, eo "oneliner": /bin/sh -c 'echo $$>/tmp/my.pid && exec program args' &- sysfault 24 de novembro '10 às 14:28
IMZ - Ivan Zakharyaschev
148

Você pode usar o jobs -lcomando para chegar a um jobL específico

^Z
[1]+  Stopped                 guard

my_mac:workspace r$ jobs -l
[1]+ 46841 Suspended: 18           guard

Nesse caso, 46841 é o PID.

De help jobs:

-l Relate o ID do grupo de processos e o diretório de trabalho dos trabalhos.

jobs -p é outra opção que mostra apenas os PIDs.

jldupont
fonte
Para usar isso em um script de shell, você teria que processar a saída.
Phil
6
@ Phil Para listar apenas os pids: jobs -p. Para listar o pid de um determinado trabalho: jobs -p% 3. Não há necessidade de processar a saída.
Erik Aronesty
11
@ Erik com diferentes comandos / argumentos, você altera o contexto do meu comentário. Sem o argumento adicional sugerido, a saída requer processamento. Sugira uma melhoria para a resposta!
28414 Phil
Salvar o PID $!logo após o início é mais portátil e direto na maioria das situações. É isso que a resposta atualmente aceita faz.
Tripleee 18/09/17
jobs -pretornou o mesmo que jobs -lno Lubuntu 16.4
Timo
46
  • $$ é o pid do script atual
  • $! é o pid do último processo em segundo plano

Aqui está um exemplo de transcrição de uma sessão do bash ( %1refere-se ao número ordinal do processo em segundo plano, conforme visto em jobs):

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100
passarela
fonte
echo %1não retorna o processo de fundo no meu Ubuntu enquanto echo $!faz
Timo
25

Uma maneira ainda mais simples de eliminar todo o processo filho de um script bash:

pkill -P $$

O -Psinalizador funciona da mesma maneira com pkille pgrep- obtém processos filhos, apenas com pkillos processos filhos sendo mortos e com pgrepos PIDs filhos impressos no stdout.

Alexey Polonsky
fonte
Muito conveniente! Essa é a melhor maneira de garantir que você não deixe processos abertos em segundo plano.
quer
@ lepe: Não é bem assim. Se você é um avô, isso não funciona: depois de bash -c 'bash -c "sleep 300 &"' & correr pgrep -P $$não mostra nada, porque o sono não será um filho direto do seu shell.
petre
11
@AlexeyPolonsky: deveria ser: matar todos os processos filhos de um shell, não de um script. Porque $$se refere ao shell atual.
Timo
executando bash -c 'bash -c "sleep 300 &"' & ; pgrep -P $$, eu entro no stdout [1] <pid>. So at least it shows something, but this is probably not the output of pgrep`
Timo
Até processos podem desanexá-los do processo pai. O truque simples é chamar o fork (criar um neto) e deixar o processo filho sair enquanto o neto continua fazendo o trabalho. (daemonization é a palavra-chave) Mas mesmo que a criança continue executando muito pkill -P não é suficiente para propagar o sinal aos netos. É necessária uma ferramenta como o pstree para seguir toda a árvore de processos dependentes. Mas isso não vai pegar daemons iniciados a partir do processo como seu pai é um processo 1. por exemplo:bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$
Pauli Nieminen
4

isto é o que eu fiz. Confira, espero que possa ajudar.

#!/bin/bash
#
# So something to show.
echo "UNO" >  UNO.txt
echo "DOS" >  DOS.txt
#
# Initialize Pid List
dPidLst=""
#
# Generate background processes
tail -f UNO.txt&
dPidLst="$dPidLst $!"
tail -f DOS.txt&
dPidLst="$dPidLst $!"
#
# Report process IDs
echo PID=$$
echo dPidLst=$dPidLst
#
# Show process on current shell
ps -f
#
# Start killing background processes from list
for dPid in $dPidLst
do
        echo killing $dPid. Process is still there.
        ps | grep $dPid
        kill $dPid
        ps | grep $dPid
        echo Just ran "'"ps"'" command, $dPid must not show again.
done

Em seguida, basta executá-lo como: ./bgkill.shcom permissões apropriadas, é claro

root@umsstd22 [P]:~# ./bgkill.sh
PID=23757
dPidLst= 23758 23759
UNO
DOS
UID        PID  PPID  C STIME TTY          TIME CMD
root      3937  3935  0 11:07 pts/5    00:00:00 -bash
root     23757  3937  0 11:55 pts/5    00:00:00 /bin/bash ./bgkill.sh
root     23758 23757  0 11:55 pts/5    00:00:00 tail -f UNO.txt
root     23759 23757  0 11:55 pts/5    00:00:00 tail -f DOS.txt
root     23760 23757  0 11:55 pts/5    00:00:00 ps -f
killing 23758. Process is still there.
23758 pts/5    00:00:00 tail
./bgkill.sh: line 24: 23758 Terminated              tail -f UNO.txt
Just ran 'ps' command, 23758 must not show again.
killing 23759. Process is still there.
23759 pts/5    00:00:00 tail
./bgkill.sh: line 24: 23759 Terminated              tail -f DOS.txt
Just ran 'ps' command, 23759 must not show again.
root@umsstd22 [P]:~# ps -f
UID        PID  PPID  C STIME TTY          TIME CMD
root      3937  3935  0 11:07 pts/5    00:00:00 -bash
root     24200  3937  0 11:56 pts/5    00:00:00 ps -f
Luis Ramirez
fonte
3

Você também pode usar o pstree:

pstree -p user

Isso normalmente fornece uma representação em texto de todos os processos para o "usuário" e a opção -p fornece a identificação do processo. Até onde eu entendo, não depende de os processos pertencerem ao shell atual. Também mostra garfos.

villaa
fonte
11
Para usar isso em um script de shell, você teria que processar pesadamente a saída.
Phil
1

pgreppode obter todos os PIDs filhos de um processo pai. Como mencionado anteriormente, $$os scripts atuais são PID. Portanto, se você deseja um script que seja limpo depois de si mesmo, isso deve ser o que você precisa:

trap 'kill $( pgrep -P $$ | tr "\n" " " )' SIGINT SIGTERM EXIT
errant.info
fonte
Isso não mataria o lote?
Phil
Sim, a pergunta nunca mencionou manter algumas crianças de segundo plano vivas na saída.
errant.info
trap 'pkill -P $$' SIGING SIGTERM EXITparece mais simples, mas eu não testei.
petre
Para compatibilidade, não use o SIGprefixo. É permitido pelo POSIX, mas apenas como uma extensão que implementações podem apoiar: pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html O shell traço por exemplo, não faz.
Josch