防止重复脚本同时运行

我正在使用scrapy来获取一些资源,我想把它作为一个可以每30分钟启动一次的cron工作。

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 

如图所示,我试图杀死脚本进程和子进程(scrapy)。

但是,当我尝试运行两个脚本时,脚本的较新实例不会终止旧实例。

如何解决?


更新:

我有一个以上的.sh scrapy脚本,它以cron配置的不同频率运行。


更新2 – 测试Serg的答案:

在我运行测试之前,所有cron作业都已停止。

然后我打开三个终端窗口,说它们被命名为w1 w2和w3,并按以下顺序运行命令:

 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) 

此时,我必须使用pkill scrapy来停止使用id为1234 scrapy

更好的方法是使用包装器脚本,它将调用主脚本。 这看起来像这样:

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

当然包装必须以不同的名称命名。 这样, pkill只能搜索你的主脚本。 这样你的主脚本就减少了:

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

请注意,在我的示例中,我使用./因为脚本位于我当前的工作目录中。 使用脚本的完整路径以获得最佳结果

我已经使用一个简单的主脚本测试了这种方法,该脚本只需运行无限循环和包装脚本。 正如您在屏幕截图中看到的那样,启动第二个包装器实例之前已经杀死了

在此处输入图像描述

你的脚本

这只是一个例子。 请记住,我无法使用scrapy进行实际测试,因此请根据您的需要进行调整。

您的cron条目应如下所示:

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

scrapy_wrapper.sh内容

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

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 

也许您应该通过创建父shell脚本pid文件来监视脚本是否正在运行,并尝试通过检查pid文件来终止以前运行的父shell脚本。 这样的事情

 #!/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" 

如果我理解你正在做什么,你想每30分钟调用一个进程(通过cron)。 但是,当您通过cron启动新进程时,您想要杀死仍在运行的任何现有版本吗?

您可以使用“timeout”命令确保如果在30分钟后仍然运行,则强制终止。

这会使你的脚本看起来像这样:

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

注意在最后一行添加的超时

我将持续时间设置为“30米”(30分钟)。 您可能希望选择稍短的时间(例如29米)以确保在下一个作业开始之前该过程已终止。

请注意,如果更改crontab中的生成间隔,则还必须编辑脚本

由于pkill仅终止指定的进程,我们应该使用-P选项终止其子进程。 所以修改后的脚本将如下所示:

 #!/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在事件EXIT上运行定义的命令(双引号),即run_scrapy.sh终止时。 还有其他事件,你会在help trap找到它们。
pgrep -o使用定义的名称查找进程的最旧实例。

PS你对grep -v $$想法是好的,但它不会返回run_scrapy.sh的其他实例的run_scrapy.sh ,因为$$将是子$(pgrep run_scrapy.sh | grep -v $$)的PID $(pgrep run_scrapy.sh | grep -v $$) ,而不是启动它的run_scrapy.sh的PID。 这就是我使用另一种方法的原因。
PPS你会在这里找到一些在Bash中终止子进程的方法。

好吧,我在使用popen()时遇到类似的问题,并且喜欢在超时父母和所有孩子之后杀死。 诀窍是设置进程组ID,同时启动父进程不要自杀。 如何做到这一点可以在这里阅读: https ://stackoverflow.com/questions/6549663/how-to-set-process-group-of-a-shell-script with“ps -eo pid,ppid,cmd,etime “你可以沿运行时过滤。 因此,对于这两种信息,您应该能够过滤所有旧进程并将其终止。

您可以检查环境变量以跟踪脚本的状态,并在脚本启动时适当地设置它,例如这个伪代码:

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

您还可以通过创建/检查/删除标记文件(如touch /pathname/myscript.is.running来跟踪状态,并在启动时使用if和rm /pathname/myscript.is.running

这种方法允许您为不同的scrapy脚本使用不同的标识符,以避免杀死错误的标识符。

无论您如何跟踪脚本的状态以及是否通过阻止启动或终止正在运行的进程来处理问题,我相信使用@JacobVlijm和@Serg建议的包装脚本将使您的生活更轻松。