2010-07-27 16 views
12

Considérese el siguiente ejemplo simplificado:Cómo obtener el PID de un proceso en una tubería

 

my_prog|awk '...' > output.csv & 
my_pid="$!" #Gives the PID for awk instead of for my_prog 
sleep 10 
kill $my_pid #my_prog still has data in its buffer that awk never saw. Data is lost! 
 

En bash, $my_pid puntos para el PID para awk. Sin embargo, necesito el PID para my_prog. Si elimino awk, my_prog no sabe si va a descargar su búfer de salida y se pierden datos. Entonces, ¿cómo se obtendría el PID para my_prog? Tenga en cuenta que ps aux|grep my_prog no funcionará, ya que puede haber varias my_prog activas.

NOTA: cambió cat por awk '...' para ayudar a aclarar lo que necesito.

+0

¿por qué es necesario canalizar a gato? – ghostdog74

+1

Realmente no canalizo a gato, esto es solo un ejemplo simplificado. Es realmente un script awk feo, pero ambos se comportan de la misma manera. – User1

+0

¿Qué estás tratando de lograr?Estoy seguro de que debe haber una mejor manera. – msw

Respuesta

4

Pude resolverlo nombrando explícitamente la tubería usando mkfifo.

Paso 1: mkfifo capture.

Paso 2: Ejecutar este script

 

my_prog > capture & 
my_pid="$!" #Now, I have the PID for my_prog! 
awk '...' capture > out.csv & 
sleep 10 
kill $my_pid #kill my_prog 
wait #wait for awk to finish. 
 

no me gusta la gestión de tener un mkfifo. Con suerte, alguien tiene una solución más fácil.

+0

¿por qué está matando un proceso cuyo resultado desea? – msw

+0

El proceso es un programa de monitoreo de hardware que se ejecutará hasta que se elimine. Cuando el proceso recibe la señal de muerte, vacía su memoria intermedia. En realidad, la secuencia de comandos bash matará a my_prog cuando finalice la prueba, que se representa mediante la instrucción sleep anterior. – User1

2

Según su comentario, todavía no puedo ver por qué prefiere matar my_prog para que se complete de manera ordenada. Diez segundos es una medida bastante arbitraria en un sistema de multiprocesamiento mediante el cual my_prog podría generar 10k líneas o 0 líneas de salida dependiendo de la carga del sistema.

Si desea limitar la salida de my_prog a algo más determinada tratar

my_prog | head -1000 | awk 

sin separarse de la cáscara. En el peor de los casos, head cerrará su entrada y my_prog obtendrá un SIGPIPE. En el mejor de los casos, cambie my_prog para obtener la cantidad de salida que desea.

añadió en respuesta a comentar:

En la medida en que usted tiene control sobre my_prog darle un argumento opcional -s duration. A continuación, en algún lugar de su bucle principal se puede poner el predicado:

if (duration_exceeded()) { 
    exit(0); 
} 

donde la salida será a su vez funcionaba correctamente los archivos de salida. Si está desesperado y no hay lugar para poner el predicado, esto podría implementarse usando la alarma (3), que intencionalmente no se muestra porque es malo.

El núcleo de su problema es que my_prog se ejecuta para siempre. Todo lo demás aquí es un truco para superar esa limitación.

+1

Ver mi comentario en mi respuesta. Creo que podría haber dado más detalles sobre la pregunta original. La solución anterior podría funcionar para algunos, pero este caso es un poco diferente. Gracias por toda su ayuda hasta ahora. Espero que puedas contarme una solución más fácil que mi respuesta. – User1

4

Agregue un contenedor de shell alrededor de su comando y capture el pid. Para mi ejemplo, uso iostat.

#!/bin/sh 
echo $$ > /tmp/my.pid 
exec iostat 1 

Exec reemplaza el shell con el nuevo proceso preservando el pid.

test.sh | grep avg 

Mientras que corre:

$ cat my.pid 
22754 
$ ps -ef | grep iostat 
userid 22754 4058 0 12:33 pts/12 00:00:00 iostat 1 

para que pueda:

sleep 10 
kill `cat my.pid` 

es que más elegante?

+0

+1, Contenedor. Esta es realmente una esencia del diseño de UNIX. – Anders

+0

No, el envoltorio es innecesariamente complicado. Hay varias otras soluciones – yaccz

5

Aquí hay una solución sin envoltorios o archivos temporales. Esto solo funciona para una canalización en segundo plano cuyo resultado se captura fuera de stdout del script que lo contiene, como en su caso. Supongamos que quieres hacer:

cmd1 | cmd2 | cmd3 >pipe_out & 
# do something with PID of cmd2 

Si tan solo golpe podría proporcionar ${PIPEPID[n]} !! La sustitución "hack" que he encontrado es la siguiente:

PID=$({ cmd1 | { cmd2 0<&4 & echo $! >&3 ; } 4<&0 | cmd3 >pipe_out & } 3>&1 | head -1) 

Si es necesario, también puede cerrar la fd 3 (para cmd*) y FD 4 (para cmd2) con 3>&- y 4<&-, respectivamente. Si haces eso, por cmd2 asegúrate de cerrar fd 4 solo después de redirige fd 0 desde él.

7

Acabo de tener el mismo problema. Mi solución:

process_1 | process_2 & 
PID_OF_PROCESS_2=$! 
PID_OF_PROCESS_1=`jobs -p` 

Solo asegúrese de que el proceso_1 sea el primero. De lo contrario, debe analizar la salida completa de jobs -l.

+0

Puede analizar de esta manera si realiza trabajos -l. Más tarde make: PID_OF_PROCESS_1 = 'jobs -l | grep process_1 | corte -f2 -d "" ' – rfranr

0

Estaba buscando desesperadamente una buena solución para obtener todos los PID de una tubería, y un enfoque prometedor falló miserablemente (ver revisiones previas de esta respuesta).

Así que, por desgracia, lo mejor que podría llegar a está analizando la salida jobs -l usando GNU awk:

function last_job_pids { 
    if [[ -z "${1}" ]] ; then 
     return 
    fi 

    jobs -l | awk ' 
     /^\[/ { delete pids; pids[$2]=$2; seen=1; next; } 
     // { if (seen) { pids[$1]=$1; } } 
     END { for (p in pids) print p; }' 
} 
1

Con la inspiración de @ respuesta de Demosthenex: utilizando subniveles:

$ (echo $BASHPID > pid1; exec vmstat 1 5) | tail -1 & 
[1] 17371 
$ cat pid1 
17370 
$ pgrep -fl vmstat 
17370 vmstat 1 5 
3

Mejorar @Marvin 's y @Nils Goroll' s respuestas con un oneliner que extrae los pids para todos los comandos en la tubería en una variable de matriz de shell:

# run some command 
ls -l | rev | sort > /dev/null & 

# collect pids 
pids=(`jobs -l % | egrep -o '^(\[[0-9]+\]\+| ) [ 0-9]{5} ' | sed -e 's/^[^ ]* \+//' -e 's! $!!'`) 

# use them for something 
echo pid of ls -l: ${pids[0]} 
echo pid of rev: ${pids[1]} 
echo pid of sort: ${pids[2]} 
echo pid of first command e.g. ls -l: $pids 
echo pid of last command e.g. sort: ${pids[-1]} 

# wait for last command in pipe to finish 
wait ${pids[-1]} 

En mi solución ${pids[-1]} contiene el valor normalmente disponible en $!. Tenga en cuenta el uso de jobs -l % que genera solo el trabajo "actual", que por defecto es el último iniciado.

Salida de ejemplo:

pid of ls -l: 2725 
pid of rev: 2726 
pid of sort: 2727 
pid of first command e.g. ls -l: 2725 
pid of last command e.g. sort: 2727 

ACTUALIZACIÓN 13/11/2017: mejorado el comando pids=... que funciona mejor con comandos (multilínea) complejos.

Cuestiones relacionadas