¿Cómo obtener la identificación del proceso del proceso en segundo plano?

375

Comienzo un proceso en segundo plano desde mi script de shell, y me gustaría eliminar este proceso cuando finalice mi script.

¿Cómo obtener el PID de este proceso desde mi script de shell? Por lo que puedo ver, la variable $!contiene el PID del script actual, no el proceso en segundo plano.

Volodymyr Bezuglyy
fuente
8
PS es correcto. ¿Estás seguro de que estás comenzando el script en BG? muestra por favor.
pixelbeat
77
Si $! es correcto. Estaba equivocado.
Volodymyr Bezuglyy
11
$$ contiene el script actual PID.
HUB

Respuestas:

583

Debe guardar el PID del proceso en segundo plano en el momento en que lo inicia:

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

No puede usar el control de trabajos, ya que es una función interactiva y está vinculada a un terminal de control. Un script no necesariamente tendrá un terminal conectado, por lo que el control del trabajo no estará necesariamente disponible.

camh
fuente
22
¡Desde $! devuelve el último pid del proceso en segundo plano. ¿Es posible que algo comience entre fooy $!, y obtengamos ese algo pid en lugar de foo's?
WiSaGaN
53
@WiSaGaN: No. No hay nada entre esas líneas. Cualquier otra actividad en el sistema no afectará esto. PS se expandirá al PID del último proceso en segundo plano en ese shell .
camh
8
... qué mangueras si fooresulta que son múltiples comandos canalizados (por ejemplo tail -f somefile.txt | grep sometext). En tales casos, obtendrá el PID del comando grep en $!lugar del comando tail si eso es lo que estaba buscando. Deberá usar jobso me psgusta en esta instancia.
John Rix
1
@ JohnRix: No necesariamente. Obtendrá el pid de grep, pero si mata eso, la cola obtendrá un SIGPIPE cuando intente escribir en la tubería. Pero tan pronto como intente entrar en cualquier gestión / control de proceso complicado, bash / shell se vuelve bastante doloroso.
camh
55
Se sugiere otra solución digna en (un comentario a una respuesta a) Cómo obtener el pid del proceso recién iniciado : oh, y el "oneliner": /bin/sh -c 'echo $$>/tmp/my.pid && exec program args' &- sysfault 24 de noviembre de 10 a 14:28
imz - Ivan Zakharyaschev
148

Puede usar el jobs -lcomando para llegar a un trabajo en particular.

^Z
[1]+  Stopped                 guard

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

En este caso, 46841 es el PID.

De help jobs:

-l Informar el ID del grupo de procesos y el directorio de trabajo de los trabajos.

jobs -p es otra opción que muestra solo los PID.

jldupont
fuente
Para usar esto en un script de shell, tendría que procesar la salida.
Phil
66
@Phil Para enumerar solo los pids: jobs -p. Para enumerar pid de un determinado trabajo: jobs -p% 3. No es necesario procesar la salida.
Erik Aronesty
1
@Erik con diferentes comandos / argumentos cambias el contexto de mi comentario. Sin el argumento adicional sugerido, la salida requiere procesamiento. ¡Sugiera una mejora a la respuesta!
Phil
Guardar el PID $!justo después de comenzar es más portátil y más sencillo en la mayoría de las situaciones. Eso es lo que hace la respuesta actualmente aceptada.
tripleee
jobs -pdevuelto lo mismo que jobs -len Lubuntu 16.4
Timo
46
  • $$ es el pid del script actual
  • $! es el pid del último proceso en segundo plano

Aquí hay una transcripción de muestra de una sesión bash (se %1refiere al número ordinal del proceso de fondo como se ve desde jobs):

$ echo $$
3748

$ sleep 100 &
[1] 192

$ echo $!
192

$ kill %1

[1]+  Terminated              sleep 100
pasadizo
fuente
echo %1no devuelve el proceso en segundo plano en mi Ubuntu mientras que lo echo $!hace
Timo
25

Una forma aún más simple de eliminar todos los procesos secundarios de un script bash:

pkill -P $$

El -Pindicador funciona de la misma manera con pkilly pgrep: obtiene procesos secundarios, solo cuando pkilllos procesos pgrepsecundarios se eliminan y con los PID secundarios se imprimen en stdout.

Alexey Polonsky
fuente
¡Muy conveniente! Esta es la mejor manera de garantizar que no deje procesos abiertos en segundo plano.
lepe
@lepe: No del todo. Si eres abuelo, esto no funcionará: después de bash -c 'bash -c "sleep 300 &"' & correr pgrep -P $$no muestra nada, porque el sueño no será un hijo directo de tu caparazón.
Petre
1
@AlexeyPolonsky: debería ser: elimine todos los procesos secundarios de un shell, no un script. Porque se $$refiere al shell actual.
Timo
actuando bash -c 'bash -c "sleep 300 &"' & ; pgrep -P $$, me pongo en stdout [1] <pid>. So at least it shows something, but this is probably not the output of pgrep`
Timo
Incluso los procesos pueden separarlos del proceso padre. El truco simple es llamar a fork (crear un nieto) y luego dejar que el proceso hijo salga mientras el nieto continúa haciendo el trabajo. (daemonization es la palabra clave) Pero incluso si el niño sigue corriendo demasiado pkill -P no es suficiente para propagar la señal a los nietos. Se requiere una herramienta como pstree para seguir todo el árbol de procesos dependientes. Pero esto no atrapará a los demonios que comenzaron en el proceso, ya que sus padres son el proceso 1. Por ejemplo:bash -c 'bash -c "sleep 10 & wait $!"' & sleep 0.1; pstree -p $$
Pauli Nieminen
4

Esto es lo que he hecho. Compruébalo, espero que pueda ayudarte.

#!/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

Luego, ejecútelo como: ./bgkill.shcon los permisos adecuados, por supuesto

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
fuente
3

También puede usar pstree:

pstree -p user

Normalmente, esto proporciona una representación de texto de todos los procesos para el "usuario" y la opción -p proporciona la identificación del proceso. No depende, por lo que entiendo, de que los procesos sean propiedad del shell actual. También muestra tenedores.

villaa
fuente
1
Para usar esto en un script de shell, tendrías que procesar mucho la salida.
Phil
1

pgreppuede obtener todos los PID secundarios de un proceso principal. Como se mencionó anteriormente, se $$encuentran los scripts actuales PID. Entonces, si desea un script que se limpie después de sí mismo, esto debería hacer el truco:

trap 'kill $( pgrep -P $$ | tr "\n" " " )' SIGINT SIGTERM EXIT
errant.info
fuente
¿Esto no mataría al lote?
Phil
Sí, la pregunta nunca mencionó mantener vivos a algunos niños de fondo al salir.
errant.info
trap 'pkill -P $$' SIGING SIGTERM EXITParece más simple, pero no lo probé.
petre
Por compatibilidad, no use el SIGprefijo. POSIX lo permite, pero solo como una extensión que las implementaciones pueden admitir: pubs.opengroup.org/onlinepubs/007904975/utilities/trap.html El shell del guión, por ejemplo, no.
josch