2010-07-13 9 views
11

Quiero comenzar un par de trabajos en diferentes máquinas usando ssh. Si el usuario interrumpe la secuencia de comandos principal, quiero cerrar todos los trabajos correctamente.Iniciar un proceso en ssh usando bash y luego matarlo en sigint

Aquí es un breve ejemplo de lo que estoy tratando de hacer:

#!/bin/bash 
trap "aborted" SIGINT SIGTERM 
aborted() { 
    kill -SIGTERM $bash2_pid 
    exit 
} 

ssh -t remote_machine /foo/bar.sh & 
bash2_pid=$! 
wait 

Sin embargo, el proceso de bar.sh sigue en funcionamiento la máquina remota. Si hago los mismos comandos en una ventana de terminal, se cierra el proceso en el host remoto.

¿Hay alguna manera fácil de que esto suceda cuando ejecuto el script bash? ¿O necesito hacerlo para iniciar sesión en la máquina remota, encontrar el proceso correcto y matarlo de esa manera?

edición: Parece que tengo que ir con la opción B, matando al remotescript a través de otra conexión ssh

Así que no quiero saber cómo puedo obtener el remotepid? He probado algo en la línea de:

remote_pid=$(ssh remote_machine '{ /foo/bar.sh & } ; echo $!') 

Esto no funciona ya que bloquea.

¿Cómo espero a que se imprima una variable y luego "libere" un subproceso?

+1

¿Has probado una trampa en tu script remoto? Tal vez SIGHUP? –

+0

He tratado de atrapar todas las señales que puedo pensar sin ningún éxito ... Supongo que tendré que volver a conectarme a la máquina remota y ejecutar kill en la secuencia de comandos, pero entonces necesito el pid de el proceso remoto ... – getekha

+0

Prueba 'pkill' si esa es la forma en que vas a ir. Crudo pero usualmente efectivo. – bstpierre

Respuesta

16

Definitivamente sería preferible mantener su limpieza administrada por el ssh que inicia el proceso en lugar de pasar a la muerte con una segunda sesión ssh más adelante.

Cuando ssh está conectado a su terminal; se comporta bastante bien Sin embargo, sepárela de su terminal y se convierte (como habrá notado) en un problema para señalar o administrar procesos remotos. Puede cerrar el enlace, pero no los procesos remotos.

Eso le deja con una opción: utilizar el enlace como una forma para que el proceso remoto sea notificado de que debe cerrarse. La forma más limpia de hacerlo es mediante el bloqueo de E/S. Realice la entrada de lectura remota desde ssh y cuando desee que el proceso se cierre; enviar algunos datos para que la operación de lectura del control remoto desbloquea y se puede proceder a la limpieza:

command & read; kill $! 

Esto es lo que nos gustaría que se ejecute en el mando a distancia. Invocamos nuestro comando que queremos ejecutar de forma remota; leemos una línea de texto (bloques hasta que recibamos uno) y cuando terminemos, señale el comando para terminar.

Para enviar la señal de nuestro script local al control remoto, todo lo que tenemos que hacer ahora es enviar una línea de texto. Desafortunadamente, Bash no te da muchas opciones buenas, aquí. Al menos, no si quieres ser compatible con bash < 4.0.

con bash 4 podemos utilizar co-procesos:

coproc ssh [email protected] 'command & read; kill $!' 
trap 'echo >&"${COPROC[1]}"' EXIT 
... 

Ahora, cuando el script local sale (no haga trampa en INT, TERM, etc Sólo EXIT) envía una nueva línea a la archivo en el segundo elemento de la matriz COPROC. Ese archivo es una tubería que está conectada a ssh 's stdin, encaminando efectivamente nuestra línea al ssh. El comando remoto lee la línea, finaliza el comando read y kill.

Antes de bash 4 las cosas se ponen un poco más difíciles ya que no tenemos coprocesos. En ese caso, tenemos que hacer la tubería de nosotros mismos:

mkfifo /tmp/mysshcommand 
ssh [email protected] 'command & read; kill $!' < /tmp/mysshcommand & 
trap 'echo > /tmp/mysshcommand; rm /tmp/mysshcommand' EXIT 

Esto debería funcionar en casi cualquier versión de bash.

+0

Esto debería funcionar. La secuencia de comandos debe ejecutarse en Bash 3.2, así que tendré que hacer mi propia tubería. El caso normal es que el proceso remoto finaliza normalmente, así que no puedo confiar en la trampa para limpiar el fifo. (Podría hacerlo si los tengo a todos en una lista e hice la limpieza al final del programa, pero preferiría eliminarlos tan pronto como termine con ellos). – getekha

+0

Esto funciona pero el problema viene ahora si el programa finaliza normalmente: la invocación de lectura aún necesita una entrada para finalizar. ¿Alguna idea de cómo cubrir ambos casos? – jkp

+0

@jkp: EXIT se activa cuando el programa también finaliza normalmente. Debe cubrir ambos casos. – lhunath

-1

Es posible que desee considerar el montaje del sistema de archivos remoto y ejecutar el script desde el cuadro maestro. Por ejemplo, si su núcleo está compilado con fusible (se puede comprobar con la siguiente):

/sbin/lsmod | grep -i fuse 

A continuación, puede montar el sistema de archivos remoto con el siguiente comando:

sshfs [email protected]_system: mount_point 

Ahora basta con ejecutar la secuencia de comandos en el archivo ubicado en mount_point.

+0

. El propósito de la secuencia de comandos real es distribuir el trabajo a través de un puñado de máquinas esclavas, así que esto no funcionaría. yo .. – getekha

6

Prueba esto:

ssh -tt host command </dev/null & 

Cuando se mata el proceso local ssh, el PTY remota se cerrará y SIGHUP será enviado al proceso remoto.

+0

Esto no es ideal para secuencias de comandos, especialmente cuando se conecta la secuencia de comandos a ssh, hará que imprima la línea de salida como si fuera una terminal interactiva. –

0

La solución para la fiesta de 3.2:

mkfifo /tmp/mysshcommand 
ssh [email protected] 'command & read; kill $!' < /tmp/mysshcommand & 
trap 'echo > /tmp/mysshcommand; rm /tmp/mysshcommand' EXIT 

no funciona. El comando ssh no está en la lista ps en la máquina "cliente". Solo después de hacerme eco de algo en la tubería, aparecerá en la lista de procesos de la máquina cliente. El proceso que aparece en la máquina "servidor" sería simplemente el comando en sí, no la parte de lectura/muerte.

Escribir de nuevo en la tubería no finaliza el proceso.

Resumiendo, tengo que escribir en la tubería para que se inicie el comando, y si escribo de nuevo, no mata el comando remoto, como se esperaba.

1

referencia a la respuesta por lhunath y https://unix.stackexchange.com/questions/71205/background-process-pipe-input me ocurrió con este script

run.sh:

#/bin/bash 
log="log"                     
eval "[email protected]" \&                    
PID=$!                      
echo "running" "[email protected]" "in PID $PID"> $log             
{ (cat <&3 3<&- >/dev/null; kill $PID; echo "killed" >> $log) & } 3<&0        
trap "echo EXIT >> $log" EXIT                
wait $PID 

la diferencia de que esta versión mata el proceso cuando se cierra la conexión, pero también devuelve el código de salida del comando cuando se ejecuta hasta su finalización.

$ ssh localhost ./run.sh true; echo $?; cat log 
0 
running true in PID 19247 
EXIT 

$ ssh localhost ./run.sh false; echo $?; cat log 
1 
running false in PID 19298 
EXIT 

$ ssh localhost ./run.sh sleep 99; echo $?; cat log 
^C130 
running sleep 99 in PID 20499 
killed 
EXIT 

$ ssh localhost ./run.sh sleep 2; echo $?; cat log 
0 
running sleep 2 in PID 20556 
EXIT 

Para una sola línea:

ssh localhost "sleep 99 & PID=\$!; { (cat <&3 3<&- >/dev/null; kill \$PID) & } 3<&0; wait \$PID" 

Para mayor comodidad:

HUP_KILL="& PID=\$!; { (cat <&3 3<&- >/dev/null; kill \$PID) & } 3<&0; wait \$PID" 
ssh localhost "sleep 99 $HUP_KILL" 

Nota: matar a 0 puede ser preferible para matar $ PID en función del comportamiento sea necesario con respecto a dio lugar a procesos secundarios. También puedes matar -HUP o matar -INT si lo deseas.

Actualización: Un canal de control de trabajo secundario es mejor que leer desde stdin.

ssh -n -R9002:localhost:8001 -L8001:localhost:9001 localhost ./test.sh sleep 2 

Establece el modo de control de trabajos y supervisar el canal de control de trabajos:

set -m 
trap "kill %1 %2 %3" EXIT 
(sleep infinity | netcat -l 127.0.0.1 9001) & 
(netcat -d 127.0.0.1 9002; kill -INT $$) & 
"[email protected]" & 
wait %3 

Por último, aquí hay otro enfoque y una referencia a un error presentada el openssh: https://bugzilla.mindrot.org/show_bug.cgi?id=396#c14

Este es el mejor camino que he encontrado para hacer esto. Desea algo del lado del servidor que intente leer stdin y luego mate el grupo de procesos cuando eso falle, pero también quiere un stdin en el lado del cliente que bloquee hasta que el proceso del lado del servidor finalice y no deje procesos persistentes como < (dormir infinito) podría.

ssh localhost "sleep 99 < <(cat; kill -INT 0)" <&1 

No parece hecho para redirigir la salida estándar en cualquier parte, pero lo hace funcionar como una entrada de bloqueo y evita la captura de pulsaciones de teclas.

Cuestiones relacionadas