防止重复脚本同时运行

防止重复脚本同时运行

我正在使用 scrapy 来获取一些资源,并且我想让它成为cron每 30 分钟启动一次的工作。

计划任务:

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 <kbd>Ctrl</kbd>+<kbd>C</kbd> in w2 (twice)

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

此刻,我必须使用pkill scrapyid 为 的 scrapy 来停止1234

答案1

更好的方法是使用包装脚本,它将调用主脚本。它看起来如下:

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

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

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

在此处输入图片描述

你的脚本

这只是一个例子。请记住,我无法访问 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

答案2

如果我理解正确的话,您希望每 30 分钟调用一次进程(通过 cron)。但是,当您通过 cron 启动新进程时,您是否想终止仍在运行的任何现有版本?

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

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

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

注意最后一行添加的超时

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

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

答案3

太棒了。稍作更新,脚本就可以自行确定自己的文件名,而无需硬编码:

#!/bin/bash 
# runchecker.sh
#this script obtains the name of the script and then
#checks if the script is already running or not
#if scripts already runs it exits

filename=$(basename $0)
echo running now $filename

pids=($(pidof -x $filename))

if [ ${#pids[@]} -gt 1 ] ; then 
                echo "Script already running by pid ${pids[1]}" 
                exit 
fi

echo "Starting service "
sleep 1000

    enter code here

答案4

由于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

trapEXIT在事件(即run_scrapy.sh终止时)上运行定义的命令(在双引号中) 。还有其他事件,您可以在 中找到它们help trap
pgrep -o查找具有定义名称的进程的最旧实例。

附言您的想法grep -v $$很好,但它不会返回其他实例的 PID run_scrapy.sh,因为$$这将是子进程的 PID $(pgrep run_scrapy.sh | grep -v $$),而不是启动它的 PID run_scrapy.sh。这就是我使用另一种方法的原因。
聚苯硫醚你会发现在 Bash 中终止子进程的其他一些方法这里

相关内容