Matar un script de shell que se ejecuta en segundo plano

12

He escrito un script de shell para monitorear un directorio usando la utilidad inotifywait de inotifyt-tools. Quiero que ese script se ejecute continuamente en segundo plano, pero también quiero poder detenerlo cuando lo desee.

Para hacerlo funcionar continuamente, solía while true; Me gusta esto:

while true;
do #a set of commands that use the inotifywait utility
end

Lo guardé en un archivo /biny lo hice ejecutable. Para que se ejecute en segundo plano, utilicé nohup <script-name> &y cerré el terminal.

No sé cómo detengo este script. He visto las respuestas aquí y una pregunta muy relacionada aquí .

ACTUALIZACIÓN 1: Sobre la base de la respuesta de @InfectedRoot a continuación, he podido resolver mi problema utilizando la siguiente estrategia. Primer uso

ps -aux | grep script_name

y usar sudo kill -9 <pid>para matar los procesos. Luego tuve que pgrep inotifywaitusarlo sudo kill -9 <pid>nuevamente para la identificación devuelta.

Esto funciona, pero creo que este es un enfoque desordenado, estoy buscando una mejor respuesta.

ACTUALIZACIÓN 2: La respuesta consiste en matar 2 procesos . Esto es importante porque al ejecutar el script en la línea de comando se inician 2 procesos, 1 el script en sí y 2, el proceso de inotificación .

light94
fuente
2
Eliminar la -9opción killy usarla solo killeliminará el desorden.
Sree
1
Bravo. Para volver a poner '$$' en la caja de juego de palabras, me gustaría enfatizar que la -9opción sería total killaquí. : P
syntaxerror
En aras de la integridad: hay un enfoque limpio para eliminar ambos procesos a la vez: en lugar de eliminar los procesos por separado, puede eliminar el grupo de procesos del script para eliminarlos de una vez. El pgid es el mismo que el PID del proceso principal script de shell y para killque el prefijo de la pgid con un signo menos: kill -- -<pgid>, kill -9 -<pgid>, etc.
cg909

Respuestas:

6

Para mejorar, usar killally también combinar los comandos:

ps -aux | grep script_name
killall script_name inotifywait

O haga todo en una línea:

killall `ps -aux | grep script_name | grep -v grep | awk '{ print $1 }'` && killall inotifywait
cremefraiche
fuente
Su solución anterior funciona pero no el comando de una línea. Por favor, ¿podrías comprobarlo? Me sale un error: no hay proceso
light94
@ light94 Error: no processdebería significar que ya lo has matado. Puede probarlo abriendo otros dos programas, como VLC y Geany , y probándolo con ellos.
cremefraiche
Hola @cremefraiche, como Earler dijo que tu código funciona ... pero he estado buscando soluciones alternativas y la solución más común que veo es usar kill <pid>. Dado que mi script no termina de esa manera, ¿estoy un poco preocupado si mi código es incorrecto? ¿Puedes por favor guiarme?
light94
Puede ahorrar en el grep -v grepconvirtiéndose grep script_nameen grep -e "[s]cript_name".
nmichaels
3

Enumerar los trabajos en segundo plano utilizando

# jobs

Luego elija el número de trabajo anterior y ejecute

ejemplo

# fg 1 

ingrese la descripción de la imagen aquí

traerá a primer plano.

Luego mátelo usando CTRL + C o una forma más fácil de encontrar el PID del script usando

ps -aux | grep script_name

ingrese la descripción de la imagen aquí

Luego mata usando pid

sudo kill -9 pid_number_here
Babin Lonston
fuente
2
La primera opción no es válida para mi caso porque cierro el shell después de indicar el script y creo que el comando jobs funciona solo para el mismo shell. Además, probé la segunda opción y mata el proceso, pero cuando hago pgrep inotifywait, todavía puedo verlo como un proceso allí.
light94
jobs dará la lista de trabajos incluso si cierra el shell. Aún así el proceso se termina, estará allí.
Babin Lonston
2
Lo intenté pero no fue así.
light94
@ light94 Tienes razón. Sucedió con frecuencia aquí que el jobscomando realmente solo mostraba los trabajos en ejecución mientras estaba en el shell. Una vez que cerré una cierta ventana de terminal, la única forma de acceder a ellos fue a través de ps(ver más arriba para eso). Entonces es seguro que no estás inventando esto.
syntaxerror
2

Puede usar ps+ grepo pgreppara obtener el nombre del proceso / pid ; luego use killall/ pkillpara matar el nombre del proceso o use killpara matar pid. Todos los siguientes deberían funcionar.

killall $(ps aux | grep script_name | grep -v grep | awk '{ print $1 }') && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $1 }' | xargs killall) && killall inotifywait
(ps -ef | grep script_name | grep -v grep | awk '{ print $2 }' | xargs kill) && killall inotifywait
(pgrep -x script_name | xargs kill) && pkill -x inotifywait
pkill -x script_name && pkill -x inotifywait

Lo más importante es que debe asegurarse de matar solo los procesos exactos que espera matar.

pkill/ pgrepcoincide con un patrón en lugar del nombre exacto , por lo que es más peligroso; aquí -xse agrega para que coincida con el nombre exacto.

Además, cuando use pgrep/ pkillpuede necesitar

  • -fpara que coincida con la línea de comando completa (como lo ps auxhace)
  • -a para imprimir también el nombre del proceso.
Hongxu Chen
fuente
Después de ejecutar lo anterior, recibo un error de uso en cada uno. Estoy copiando pegando y reemplazando el nombre del script. Obtengo uso para kill como salida
light94
¿Se está ejecutando script_name? Funciona para mí (debian jessie)
Hongxu Chen
Sí, se está ejecutando. y su solución es casi la misma que las soluciones @cremefraiche anteriores, por lo que esperaba que funcionara de la misma manera: /
light94
Sí, estaba resumiendo de alguna manera las posibles formas de hacerlo, especialmente agregando pgrep/ pkill. Si eso sigue mal, puede intentar pgrepsin -x. Básicamente, no creo que sea bueno matar el proceso dentro de un comando de línea sin verificar si otros procesos coinciden.
Hongxu Chen
1

Como probablemente pueda ver, hay muchas maneras de hacer esto.

Con respecto a su "ACTUALIZACIÓN # 2" - en términos generales, la terminación de cualquier proceso en una jerarquía padre-hijo generalmente terminará todos los procesos asociados. Pero hay muchas excepciones a esto. Idealmente, desea terminar el 'hijo' final en un árbol de procesos, luego los padres de este hijo deberían salir si no tienen otras tareas para ejecutar. Pero si matas a un padre, la señal debe transmitirse a los niños cuando el padre muere y los niños también deben salir, pero hay casos en los que los procesos hijos pueden ignorar la señal (a través de trampas o mecanismos similares) y pueden continuar ejecutar un proceso será heredado por el proceso 'init' (o similar). Pero este tema del comportamiento del proceso puede volverse complejo y lo dejaré allí ...

Un método que me gusta si no quiero usar un script de control (descrito a continuación) es usar la utilidad 'pantalla' para iniciar y administrar el proceso. El comando 'pantalla' está lleno de funciones y puede tomar algún tiempo dominarlo. Te animo a leer la página de manual de 'pantalla' para obtener una explicación completa. Un ejemplo rápido para iniciar un proceso en segundo plano sería el comando:

pantalla -d -m / ruta / a / programa

Esto iniciará "/ ruta / a / programa" dentro de una sesión de 'pantalla'.

Puedes ver tu sesión de carrera con el comando:

pantalla -ls

Y en cualquier momento puede volver a conectarse a su programa en ejecución con el comando:

pantalla -r

Y luego simplemente termine con un ^ C o lo que sea.

Además de la belleza de poder reconectarse y desconectarse de su proceso a voluntad, esa 'pantalla' capturará cualquier stdout () que su programa pueda producir.


Pero mi preferencia personal en estos asuntos es tener un programa de control que gestione el inicio y la detención de un proceso. Esto puede ser algo complicado y requiere algunas secuencias de comandos posiblemente complicadas. Y como cualquier guión, hay docenas de buenas maneras de hacerlo. He incluido un ejemplo bash de un método que uso habitualmente para iniciar y detener aplicaciones. Si su tarea es simple, puede insertarla directamente en el script de control, o puede hacer que este script de control llame a otro programa externo. Tenga en cuenta que este ejemplo no es exhaustivo en términos de gestión del proceso. He omitido la posibilidad de escenarios como: Asegurarse de que el script no se esté ejecutando cuando usa la opción "inicio", validar que el PID en ejecución es en realidad el proceso que inició (por ejemplo, su script no t murió y se inició otro proceso usando el mismo PID), y validando que el script realmente respondió (salió) en la primera solicitud 'kill'. Hacer todas estas comprobaciones puede ser complicado y no quería que el ejemplo fuera demasiado largo y complejo. Es posible que desee modificar el ejemplo para practicar sus scripts de shell.

Guarde el siguiente código en un archivo llamado "programctl", hágalo ejecutable con el comando:

chmod 755 programctl

Luego edite el archivo y agregue su código / script en la sección de caso que comienza con "myscript".

Una vez que todo esté en su lugar, suponiendo que "programctl" esté en el directorio actual, puede comenzar su programa con:

./programctl start

Y para con:

./programctl stop

Salud.

#!/bin/bash
# Description:  A wrapper script used to stop/start another script.

#--------------------------------------
# Define Global Environment Settings:
#--------------------------------------

# Name and location of a persistent PID file

PIDFILE="/tmp/tmpfile-$LOGNAME.txt"

#--------------------------------------
# Check command line option and run...
# Note that "myscript" should not
# provided by the user.
#--------------------------------------

case $1
in
    myscript)
        # This is where your script would go.
        # If this is a routine 'bash' shell script, you can enter
        # the script below as illustrated in the example.  
        # Or you could simply provide the path and parameters
        # to another script such as /dir/name/command -options

        # Example of an embedded script:

        while true
        do
            # do something over and over...
            sleep 1
        done

        # Example of an external script:

        /usr/local/bin/longrun -x
    ;;

    start)
        # Start your script in the background.
        # (Note that this is a recursive call to the wrapper
        #  itself that effectively runs your script located above.)
        $0 myscript &

        # Save the backgound job process number into a file.
        jobs -p > $PIDFILE

        # Disconnect the job from this shell.
        # (Note that 'disown' command is only in the 'bash' shell.)
        disown %1

        # Print a message indicating the script has been started
        echo "Script has been started..."
    ;;

    stop)
        # Read the process number into the variable called PID
        read PID < $PIDFILE

        # Remove the PIDFILE
        rm -f $PIDFILE

        # Send a 'terminate' signal to process
        kill $PID

        # Print a message indicating the script has been stopped
        echo "Script has been stopped..."
    ;;

    *)
        # Print a "usage" message in case no arguments are supplied
        echo "Usage: $0 start | stop"
    ;;
esac
Christopher Morris
fuente
Probé el método de "pantalla" que sugirió y funciona de maravilla. Todavía tengo que probar el segundo método. Gracias por la respuesta.
light94