web-dev-qa-db-fra.com

Empêcher l'exécution d'un script en double en même temps

J'utilise scrapy pour récupérer des ressources et je souhaite le rendre comme un travail cron pouvant commencer toutes les 30 minutes.

Le cron:

0,30 * * * * /home/us/jobs/run_scrapy.sh`

run_scrapy.sh:

#!/bin/sh
cd ~/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
pkill -f $(pgrep run_scrapy.sh | grep -v $$)
sleep 2s
scrapy crawl good

Comme le script l'a montré, j'ai également essayé de tuer le processus de script et le processus enfant (scrapy).

Toutefois, lorsque j'ai essayé d'exécuter deux scripts, l'instance la plus récente du script ne supprime pas l'instance la plus ancienne.

Comment résoudre ce problème?


Mise à jour:

J'ai plusieurs scripts .sh qui fonctionnent à différentes fréquences et configurés dans cron.


Mise à jour 2 - Test de la réponse de Serg:

Tous les travaux cron ont été arrêtés avant l'exécution du test.

Ensuite, j'ouvre trois fenêtres de terminal disant qu'elles s'appellent w1, w2 et w3, et exécute les commandes dans les ordres suivants:

Run `pgrep scrapy` in w3, which print none.(means no scrapy running at the moment).

Run `./scrapy_wrapper.sh` in w1

Run `pgrep scrapy` in w3 which print one process id say it is `1234`(means scrapy have been started by the script)

Run `./scrapy_wrapper.sh` in w2 #check the w1 and found the script have been terminated.

Run `pgrep scrapy` in w3 which print two process id `1234` and `5678`

Press `Ctrl+C` in w2(twice)

Run `pgrep scrapy` in w3 which print one process id `1234` (means scrapy of `5678` have been stopped)

À ce moment-là, je dois utiliser pkill scrapy pour mettre fin à la lecture avec l'identifiant de 1234

5
hguser

Une meilleure approche consisterait à utiliser un script wrapper, qui appellera le script principal. Cela ressemblerait à ceci:

#!/bin/bash
# This is /home/user/bin/wrapper.sh file
pkill -f 'main_script.sh'
exec bash ./main_script.sh

Bien sûr, wrapper doit être nommé différemment. De cette façon, pkill ne peut rechercher que votre script principal. De cette façon, votre script principal se réduit à ceci:

#!/bin/sh
cd /home/user/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
scrapy crawl good

Notez que dans mon exemple, j'utilise ./ parce que le script était dans mon répertoire de travail actuel. Utilisez le chemin complet de votre script pour de meilleurs résultats

J'ai testé cette approche avec un script principal simple qui exécute des scripts infinis While loop et wrapper. Comme vous pouvez le voir sur la capture d'écran, le lancement de la deuxième instance de wrapper tue les précédents

enter image description here

Votre script

Ceci est juste exemple. N'oubliez pas que je n'ai pas accès à Scrapy pour le tester, ajustez-le selon vos besoins.

Votre entrée cron devrait ressembler à ceci:

0,30 * * * * /home/us/jobs/scrapy_wrapper.sh

Contenu de scrapy_wrapper.sh

#!/bin/bash
pkill -f 'run_scrapy.sh'
exec sh /home/us/jobs/run_scrapy.sh

Contenu de run_scrapy.sh

#!/bin/bash
cd /home/user/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
# sleep delay now is not necessary
# but uncomment if you think it is
# sleep 2
scrapy crawl good
9

Peut-être devriez-vous surveiller si le script est en cours d'exécution en créant le fichier pid du script Shell parent et essayer de supprimer le précédent script Shell en cours d'exécution en vérifiant le fichier pid. Quelque chose comme ca

#!/bin/sh
PATH=$PATH:/usr/local/bin
PIDFILE=/var/run/scrappy.pid
TIMEOUT="10s"

#Check if script pid file exists and kill process
if [ -f "$PIDFILE" ]
then
  PID=$(cat $PIDFILE)
  #Check if process id is valid
  ps -p $PID >/dev/null 2>&1
  if [ "$?" -eq "0" ]
  then
    #If it is valid kill process id
    kill "$PID"
    #Wait for timeout
    sleep "$TIMEOUT"
    #Check if process is still running after timeout
    ps -p $PID >/dev/null 2>&1
    if [ "$?" -eq "0" ]
    then
      echo "ERROR: Process is still running"
      exit 1
    fi
  fi 
fi

#Create PID file
echo $$ > $PIDFILE
if [ "$?" -ne "0" ]
then
  echo "ERROR: Could not create PID file"
  exit 1
fi

export PATH
cd ~/spiders/goods
scrapy crawl good
#Delete PID file
rm "$PIDFILE"
2
iuuuuan

Si je comprends bien ce que vous faites correctement, vous souhaitez appeler un processus toutes les 30 minutes (via cron). Cependant, lorsque vous démarrez un nouveau processus via cron, vous souhaitez supprimer toutes les versions existantes en cours d'exécution?

Vous pouvez utiliser la commande "timeout" pour vous assurer que si Scrappy est forcé de se terminer s'il est toujours en cours d'exécution après 30 minutes.

Cela ferait ressembler votre script à ceci:

#!/bin/sh
cd ~/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
timeout 30m scrapy crawl good

notez le délai d'attente ajouté à la dernière ligne

J'ai réglé la durée à "30m" (30 minutes). Vous voudrez peut-être choisir un temps légèrement plus court (par exemple, 29 m) pour vous assurer que le processus est terminé avant le début du travail suivant.

Notez que si vous modifiez l'intervalle d'apparition dans crontab, vous devrez également éditer le script.

2
Nick Sillito

Comme pkill ne termine que le processus spécifié, nous devrions terminer ses sous-processus enfants en utilisant l'option -P. Donc, le script modifié ressemblera à ceci:

#!/bin/sh

cd /home/USERNAME/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
PID=$(pgrep -o run_scrapy.sh)
if [ $$ -ne $PID ] ; then pkill -P $PID ; sleep 2s ; fi
scrapy crawl good

trap exécute la commande définie (entre guillemets) sur l'événement EXIT, c'est-à-dire lorsque run_scrapy.sh est terminé. Il y a d'autres événements, vous les trouverez dans help trap.
pgrep -o recherche l'instance la plus ancienne du processus portant le nom défini.

PS Votre idée avec grep -v $$ est bonne, mais elle ne vous retournera pas le PID d'une autre instance de run_scrapy.sh, car $$ sera le PID du sous-processus $(pgrep run_scrapy.sh | grep -v $$), pas le PID de run_scrapy.sh qui l'a démarré. C'est pourquoi j'ai utilisé une autre approche.
P.P.S. Vous trouverez d'autres méthodes pour mettre fin aux sous-processus dans Bash ici .

1
whtyger

Eh bien, j'ai eu un problème similaire avec l'utilisation de C en utilisant popen () et j'aime tuer après un délai d'expiration parent et tous les enfants. L'astuce consiste à définir un ID de groupe de processus lors du démarrage de votre parent pour qu'il ne me tue pas. comment faire cela peut être lu ici: https://stackoverflow.com/questions/6549663/how-to-set-process-group-of-a-Shell-script avec "ps -eo pid, ppid, cmd, etime "vous pouvez filtrer le long de l'exécution. avec les deux informations, vous devriez pouvoir filtrer tous les anciens processus et les tuer.

0
0x0C4

Vous pouvez vérifier une variable d’environnement pour suivre l’état du script et la définir de manière appropriée au démarrage du script, à l’aide du code psuedo suivant:

if "$SSS" = "Idle"
then 
    set $SSS=Running"
    your script
    set $SSS="Idle"

Vous pouvez également suivre l’état en créant/vérifiant/supprimant un fichier marqueur comme touch /pathname/myscript.is.running et en utilisant s’il existe au lancement et rm /pathname/myscript.is.running à la fin.

Cette approche vous permettra d’utiliser différents identifiants pour vos différents scripts effacés afin d’éviter de tuer les mauvais.

Quelle que soit la façon dont vous suivez l'état de votre script et que vous résolviez le problème en empêchant le lancement ou en mettant fin au processus en cours, je pense que l'utilisation d'un script wrapper, comme suggéré par @JacobVlijm & @Serg, vous facilitera la vie beaucoup plus facilement.

0
Elder Geek