Tuer un script shell exécuté en arrière-plan

12

J'ai écrit un script shell pour surveiller un répertoire en utilisant l'utilitaire inotifywait des outils inotifyt. Je veux que ce script s'exécute en continu en arrière-plan, mais je veux aussi pouvoir l'arrêter quand je le souhaite.

Pour le faire fonctionner en continu, j'ai utilisé while true; comme ça:

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

Je l'ai enregistré dans un fichier /binet l' ai rendu exécutable. Pour le faire fonctionner en arrière-plan, j'ai utilisé nohup <script-name> &et fermé le terminal.

Je ne sais pas comment arrêter ce script. J'ai regardé les réponses ici et une question très étroitement liée ici .

MISE À JOUR 1: Sur la base de la réponse de @InfectedRoot ci-dessous, j'ai pu résoudre mon problème en utilisant la stratégie suivante. Première utilisation

ps -aux | grep script_name

et utiliser sudo kill -9 <pid>pour tuer les processus. J'ai ensuite dû pgrep inotifywaitet utiliser à sudo kill -9 <pid>nouveau pour l'id retourné.

Cela fonctionne mais je pense que c'est une approche désordonnée, je cherche une meilleure réponse.

MISE À JOUR 2: La réponse consiste à tuer 2 processus . Ceci est important car l'exécution du script sur la ligne de commande lance 2 processus, 1 le script lui - même et 2, le processus inotify .

light94
la source
2
La suppression de l' -9option de killet l'utilisation de Just killenlèveront le désordre.
Sree
1
Bravo. Pour réintroduire un peu de «$$» dans la boîte de jeu de mots, je voudrais souligner que l' -9option serait totale killici. : P
erreur de syntaxe
Par souci d'exhaustivité: il existe une approche propre pour tuer les deux processus à la fois: au lieu de tuer les processus séparément, vous pouvez tuer le groupe de processus du script pour les tuer à la fois. Pgid est le même que le pid du processus principal de script shell et killil vous préfixe pgid avec moins: kill -- -<pgid>, kill -9 -<pgid>, etc.
cg909

Réponses:

6

Pour améliorer, utiliser killallet combiner également les commandes:

ps -aux | grep script_name
killall script_name inotifywait

Ou tout faire en une seule ligne:

killall `ps -aux | grep script_name | grep -v grep | awk '{ print $1 }'` && killall inotifywait
crème fraiche
la source
Votre solution ci-dessus fonctionne mais pas la commande d'une seule ligne. Veuillez le vérifier. Je reçois une erreur: aucun processus
light94
@ light94 Error: no processdevrait simplement signifier que vous l'avez déjà tué. Vous pouvez le tester en ouvrant deux autres programmes, comme VLC et Geany , et en l'essayant avec eux à la place.
cremefraiche
Hé @cremefraiche, comme l'a dit Earler, votre code fonctionne .. mais j'ai cherché des solutions alternatives et la solution la plus courante que je vois est d'utiliser kill <pid>. Depuis, mon script ne se termine pas de cette manière, je suis un peu inquiet si mon code est incorrect? Pouvez-vous me guider s'il vous plaît?
light94
Vous pouvez économiser sur l' grep -v grepen tournant grep script_namedans grep -e "[s]cript_name".
nmichaels
3

Énumérer les tâches d'arrière-plan à l'aide de

# jobs

Ensuite, choisissez le nombre précédent de travaux et exécutez

exemple

# fg 1 

entrez la description de l'image ici

mettra au premier plan.

Ensuite, tuez-le en utilisant CTRL + C ou un moyen plus facile de trouver le PID du script en utilisant

ps -aux | grep script_name

entrez la description de l'image ici

Tuez ensuite avec pid

sudo kill -9 pid_number_here
Babin Lonston
la source
2
La première option n'est pas valable pour mon cas car je ferme le shell après avoir énoncé le script et je pense que la commande jobs ne fonctionne que pour le même shell. De plus, j'ai essayé la deuxième option et elle tue le processus mais quand je fais pgrep inotifywait, je peux toujours le voir comme un processus là-bas.
light94
les travaux donneront la liste des travaux même si vous fermez le shell. Le processus est toujours terminé, il sera là.
Babin Lonston
2
J'ai essayé mais ça n'a pas marché.
light94
@ light94 Vous avez raison. Il arrivait fréquemment ici que la jobscommande n'affichait que les travaux en cours dans le shell. Une fois que j'ai fermé une certaine fenêtre de terminal, le seul moyen d'y accéder était via ps(voir ci-dessus pour cela). C'est donc sûr que vous n'inventez rien.
syntaxerror
2

Vous pouvez utiliser ps+ grepou pgreppour obtenir le nom du processus / pid ; utiliser plus tard killall/ pkillpour tuer le nom du processus ou utiliser killpour tuer pid. Tous les éléments suivants devraient fonctionner.

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

La chose la plus importante est que vous devez vous assurer de ne tuer que le ou les processus exacts que vous espérez tuer.

pkill/ pgrepcorrespond à un modèle plutôt qu'au nom exact , c'est donc plus dangereux; ici -xest ajouté pour correspondre au nom exact.

De plus, lorsque vous utilisez pgrep/ pkillvous pourriez avoir besoin

  • -fpour correspondre à la ligne de commande complète (comme le ps auxfait)
  • -a pour imprimer également le nom du processus.
Hongxu Chen
la source
Après avoir exécuté ce qui précède, j'obtiens une erreur d'utilisation dans chacun. je copie et colle le scriptname.
J'obtiens une
Le nom de script est-il en cours d'exécution? Cela fonctionne pour moi (Debian Jessie)
Hongxu Chen
oui il fonctionne. et votre solution est presque la même que les solutions @cremefraiche ci-dessus, donc je m'attendais à ce qu'elle fonctionne de la même manière: /
light94
Oui, je résumais quelque peu les moyens possibles de le faire, en particulier en ajoutant pgrep/ pkill. Si cela ne fonctionne toujours pas, vous pouvez essayer pgrepsans -x. Fondamentalement, je ne pense pas que ce soit une bonne chose de tuer le processus au sein d'une commande de ligne sans vérifier si d'autres processus correspondent.
Hongxu Chen
1

Comme vous pouvez probablement le constater, il existe de nombreuses façons de procéder.

Concernant votre "UPDATE # 2" - de manière générale, la fin de tout processus dans une hiérarchie parent-enfant mettra généralement fin à tous les processus associés. Mais il y a de nombreuses exceptions à cela. Idéalement, vous souhaitez terminer le dernier «enfant» dans une arborescence de processus, puis les parents de cet enfant doivent quitter s'ils n'ont aucune autre tâche à exécuter. Mais si vous tuez un parent, le signal doit être relayé aux enfants lorsque le parent décède et les enfants doivent également sortir - mais il y a des cas où les processus enfants peuvent ignorer le signal (via des pièges ou des mécanismes similaires) et peuvent continuer exécuter un testament sera hérité par le processus 'init' (ou similaire.) Mais ce sujet du comportement du processus peut devenir complexe et je vais le laisser là ...

Une méthode que j'aime si je ne veux pas utiliser un script de contrôle (décrit ci-dessous) est d'utiliser l'utilitaire «écran» pour démarrer et gérer le processus. La commande «écran» regorge de fonctionnalités et peut prendre un certain temps à maîtriser. Je vous encourage à lire la page de manuel «écran» pour une explication complète. Un exemple rapide pour démarrer un processus en arrière-plan serait la commande:

écran -d -m / chemin / vers / programme

Cela démarrera "/ path / to / program" à l'intérieur d'une session "écran".

Vous pouvez voir votre session en cours avec la commande:

écran -ls

Et à tout moment, vous pouvez vous reconnecter à votre programme en cours d'exécution avec la commande:

écran -r

Et puis il suffit de le terminer avec un ^ C ou autre chose.

Outre la beauté de pouvoir se reconnecter et se déconnecter de votre processus à volonté, cet «écran» capturera tout stdout () que votre programme pourrait produire.


Mais ma préférence personnelle dans ces domaines est d'avoir un programme de contrôle qui gère le démarrage et l'arrêt d'un processus. Cela peut devenir quelque peu compliqué et nécessite des scripts sans doute compliqués. Et comme tout script, il existe des dizaines de bonnes façons de le faire. J'ai inclus un exemple bash d'une méthode que j'utilise régulièrement pour démarrer et arrêter des applications. Si votre tâche est simple, vous pouvez l'insérer directement dans le script de contrôle - ou vous pouvez demander à ce script de contrôle d'appeler un autre programme externe. Notez que cet exemple n'est en aucun cas exhaustif en termes de gestion du processus. J'ai exclu la possibilité de scénarios tels que: S'assurer que le script n'est pas déjà en cours d'exécution lorsque vous utilisez l'option "démarrer", valider que le PID en cours d'exécution est bien le processus que vous avez démarré (par exemple, votre script n'a pas ' t est mort et un autre processus a été démarré en utilisant le même PID), et valider que le script a effectivement répondu (quitté) à la première demande de «kill». Faire toutes ces vérifications peut devenir compliqué et je ne voulais pas rendre l'exemple trop long et complexe. Vous souhaiterez peut-être modifier l'exemple pour vous entraîner à l'écriture de scripts shell.

Enregistrez le code suivant dans un fichier appelé "programctl", rendez-le exécutable avec la commande:

chmod 755 programctl

Modifiez ensuite le fichier et ajoutez votre code / script dans la section de cas qui commence par "myscript".

Une fois que tout est en place, en supposant que "programctl" se trouve dans le répertoire courant, vous pouvez démarrer votre programme avec:

./programctl start

Et arrêtez-le avec:

./programctl stop

À votre santé.

#!/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
la source
J'ai essayé la méthode "écran" que vous avez suggérée et elle fonctionne à merveille. Je n'ai pas encore essayé la deuxième méthode. Merci pour la réponse.
light94