Cron任务和随机时间,在给定的小时内。

142

我需要有能力在每天完全不同的时间运行一个 PHP 脚本20次。同时,我只希望它在早上9点到晚上11点之间运行。

我熟悉在 Linux 中创建 Cron 作业的方法。


3
这个问题表述不是很清楚。最终你想在上午9点到11点之间在时间轴上分配20个点。但是是否存在最小时间差的限制?在上午9点到10:30之间什么都不做是否可接受?唯一可行的方法似乎是Klaus的想法:在09:00选择你的20个时间,这样可以满足任何可能存在的限制,然后使用“at”安排事情。 - David Tonhofer
13个回答

188

如何在9点到11点之间的随机偏移量下,每天20次cron某些东西?这在cron中有点棘手,因为您将14小时分成20个执行时间。我不太喜欢其他答案,因为它们需要为您的php脚本编写bash包装器脚本。

但是,如果您允许我将时间和频率限制放宽到8:30 am至11:09 pm之间的13次,那么这可能会起作用,并且所有内容都在您的crontab范围内:

30 8-21/* * * * sleep ${RANDOM:0:2}m ; /path/to/script.php

${RANDOM:3:2}使用bash的$RANDOM,其他人已经提到过,但添加了bash数组切片。由于bash变量是无类型的,伪随机有符号16位数字被截断为其5个十进制数字中的前2个,为您提供了一个简洁的单行代码,用于在10到99分钟之间延迟cronjob(尽管分布偏向于10到32)。

以下内容也可能适用于您,但我发现它“不太随机”,原因不明(也许是通过调制伪随机数触发了本福德定律。嘿,我不知道,我数学不及格...归咎于bash吧!):

30 8-21/* * * * sleep $[RANDOM\%90]m ; /path/to/script.php

由于cron(至少是Linux“vixie-cron”)在遇到未转义的“%”时会终止该行,因此您需要将模数呈现为“%”。

也许您可以通过添加另一行具有另一个7小时范围的行来在其中获得剩余的7个脚本执行。或者放宽限制,允许在凌晨3点至晚上11点之间运行。


4
我喜欢这个晚回复。但是如果你想在10到99的范围内生成一个均匀分布的随机整数,并且RANDOM的输出范围是0到32,767,那么为什么不使用"$[(RANDOM/368)+10]"呢? - jsdalton
4
@jsdalton: 取模运算符会更好吧? $((RANDOM % 90 + 10)) 测试: for i in {0..9999}; do echo $((RANDOM % 90 + 10)); done | sort | uniq -c - geon
6
在许多系统中,cron 默认不使用 bash,因此最好避免使用 bash 的特性 $RANDOM。可以考虑使用以下命令:sleep $(( $(od -N1 -tuC -An /dev/urandom) \% 90 ))m,这样更加兼容。该命令的作用是随机延迟一段时间,单位为分钟。 - pabouk - Ukraine stay strong
12
在使用 $RANDOM 之前,请确保 crontab 正在使用 bash。如果你的 Ubuntu 系统中使用的是 vixie-cron,则可以在文件顶部添加 SHELL=/bin/bash。其他 cron 版本的更多替代方法可以在此链接找到:http://superuser.com/a/264541/260350。 - DaAwesomeP
1
当我使用上面的第一个建议时,我会得到crontab:crontab文件中的错误,无法安装。您要重试相同的编辑吗?请帮忙。 - Sean H
显示剩余3条评论

96

我现在正在使用以下代码在凌晨1点到3点30之间运行一个命令

0 1 * * * perl -le 'sleep rand 9000' && *command goes here*

那已经为我处理了一些随机需求。这是9000秒 == 150分钟 == 2.5小时。


14
"MindBLOWN":又一个使用 Perl 语言的不太常见的场合。 - Mark Carpenter Jr
2
这绝对是最简洁的答案。 - Enrico Detoma
这种方法有点低效,因为你会创建很多进程。 - kahveciderin
Ruby(Perl的宝贝)也是一个简短而不错的解决方案,如果你没有安装perl,可以使用ruby -e 'sleep rand 900' - undefined

44

如果我理解你的需求正确,你需要做一些比较麻烦的事情,比如设置一个cron作业来运行一个随机化运行时间的bash脚本...像这样:

crontab:

0 9 * * * /path/to/bashscript

并且在 /path/to/bashscript 中:

#!/bin/bash

maxdelay=$((14*60))  # 14 hours from 9am to 11pm, converted to minutes
for ((i=1; i<=20; i++)); do
    delay=$(($RANDOM%maxdelay)) # pick an independent random delay for each of the 20 runs
    (sleep $((delay*60)); /path/to/phpscript.php) & # background a subshell to wait, then run the php script
done
几点需要注意:这种方法浪费资源有些多,因为它在早上9点启动了20个后台进程,每个进程都等待随机分钟数(最长可达14小时,即晚上11点),然后启动php脚本并退出。此外,由于它使用的是随机分钟数(而不是秒),因此开始时间并不像它们本来可以那样随机。但$RANDOM只能达到32,767,并且在早上9点和晚上11点之间有50,400秒,如果要使秒也随机化,将会更加复杂。最后,由于启动时间是随机和彼此独立的,虽然不太可能,但可能会同时启动两个或多个脚本实例。

2
你可以通过去掉美元符号并将双括号移到左边来使算术赋值更易读(例如 ((maxdelay = 14 * 60))((delay = $RANDOM % maxdelay)))。sleep 参数仍然需要按照您的方式(如果需要,可以添加空格)。 - Dennis Williamson
1
这对我也起作用了。我的自定义bash脚本如下所示:sleep $[( $RANDOM % 60 ) + 1]s && some_script.sh - Shyam Habarakada
我是否漏掉了什么,或者最大延迟应该设置为maxdelay=$((14*60/20))? - Jenish
@jenishSakhiya 每个20次运行的随机延迟是绝对的(从早上9点开始),而不是相对于其他运行的延迟。也就是说,如果其中一个随机延迟为13小时,那么它将在晚上10点运行(在早上9点之后13小时),而不是在任何其他运行之后13小时。 - Gordon Davisson

32

一些cron实现提供了RANDOM_DELAY变量。

RANDOM_DELAY变量允许随机延迟作业启动的时间,上限由该变量指定。

详情请参见crontab(5)

这在anacron作业中很常见,但也可以在crontab中使用。

如果您有一些以精细(分钟)为粒度运行的作业和其他粗略的作业,则可能需要小心处理此问题。


我很想使用RANDOM_DELAY变量,但在Ubuntu 14.04.4 LTS的crontab(5)手册中找不到任何提示。 - Exocom
很不幸。我想知道它是否在那里不受支持。我在Centos 7和Arch Linux的man页面中看到了它的文档记录。 - Micah Elliott
11
这似乎是正确答案,但你能给个例子吗? - chovy
16
请注意,RANDOM_DELAY 在守护进程的整个运行期间只被设定一次,并保持不变。 - Maciej Swic
16
“RANDOM_DELAY” 标识是 cronie-crond 的一个功能,而Ubuntu似乎在运行缺少这个标识的“vixie-cron”。 - kravietz
显示剩余2条评论

11

最终我使用了 sleep $(( 1$(date +%N) % 60 )) ; dostuffs(与bash和sh兼容)。

前缀1是为了强制解释date +%N时的非8进制(例如00551454)。

在crontab文件中不要忘记使用\%转义%。

* * * * *  nobody  sleep $(( 1$(date +\%N) \% 60 )) ; dostuffs 

2
如果有人像我一样好奇:%N提供了当前的纳秒,但是一些手册缺乏相关信息。对于那些只需要每个命令轻松获取“一些随机性”的人来说,这是一个非常聪明的解决方案。 - Thorsten Schöning
1
除了其他地方已经提到的注意事项之外,这只能在您拥有GNU“date”时才能正常工作(在大多数Linux上可能会有,但在Busybox、标准MacOS或其他各种BSD衍生平台上可能没有)。 - tripleee

7

我的第一想法是创建一个cron作业,启动20个随机安排的at作业。使用at实用程序(http://unixhelp.ed.ac.uk/CGI/man-cgi?at)在指定时间执行命令。


使用 at 的示例在此处:http://stackoverflow.com/a/6136145/803174 - Kangur

5

我认为al-x的解决方案对我不起作用,因为cron命令是在sh而不是bash中执行的。下面的方法适用:

30 8 * * * bash -c "sleep $[RANDOM\%90]m" ; /path/to/script.py

我尝试了所有的方法,但仍然无法解决问题。您的帖子解释了原因,谢谢! - marlar
1
$[ ... ]自远古时代就已经被弃用了;对于这个千禧年以来的任何事情,您都会更喜欢 $((RANDOM\%90))m,这是符合POSIX标准的语法(但当然RANDOM仍然只适用于Bash)。 - tripleee

4
您可以尝试使用以下示例,在执行命令之前随机等待一段时间:
#!/bin/bash
# start time
date +"%H:%M:%S"

# sleep for 5 seconds
sleep $(shuf -i 1-25 -n 1)
# end time
date +"%H:%M:%S"

3

at -f [file] [timespec]

或者

echo [command] | at [timespec]

或者

at [timespec] ... 和交互式规范,就像 script 的记录一样。

命令

at 命令会在 stdin 中运行提供的文本,或者在指定了 -f [file] 的情况下,运行指定文件中的内容。

时间规范

这里是 [timespec] 语法。它可以像下面这样:

  • 24小时制的时间作为4位数整数,如 010023591620
  • now + 10 minutes
  • 2071-05-31 - 5 hours 12 minutes UTC

如果您明确指定时区,在 某些版本 的时间规范中可能只允许可选时区参数为 UTC

示例

cat script.sh | at now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

at -f script.sh now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

试一试...

您可以通过在命令前加上 echo 并转义 | (管道)来测试 bash 解析。

echo cat script.sh \| at now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

echo at -f script.sh now + $(($RANDOM % 10)) hours $(($RANDOM % 60)) minutes

要查看已计划的作业,请使用 atq 命令,使用 at -c [jobid] 查看作业内容(环境变量、设置和命令/脚本)。

注意事项

该系统是 cron 的一部分,并且交互式提示符实际上捕获了您的整个 shell 的当前状态,因此您可以运行不需要指定绝对路径的命令。


3

我知道这是一个比较老的主题,但我想要补充一个关于随机值的东西,因为我经常使用它。我在shell中不使用具有固定和有限范围的$RANDOM变量,而是生成任意范围内的随机值。

dd if=/dev/urandom bs=4 count=1 2>/dev/null | od -N4 -t u4 -A none

所以你可以做例如以下事情:

FULLRANDOM=$(dd if=/dev/urandom bs=4 count=1 2>/dev/null | od -N4 -t u4 -A none)

并克服了在此线程中讨论的一些限制。


1
欢迎来到SO。无论这是一个旧帖还是新帖,它都让我去了解了~$info dd,我可以理解|左侧正在发生的事情,但无法理解右侧。因此,对于我和其他有兴趣在随机值生成中“克服一些限制”的人来说,为什么不花点时间解释一下RHS,并更强烈地推荐使用您的方法呢?详细的解释可以让人们更加舒适地接受您提出的过程及其好处。谢谢。 - Chris
2
啊,好的。od(也称为“八进制转储”)接受文件或stdin,并以人类可读的形式转储二进制数据。 我们希望将数字显示为无符号十进制数(-t u4),并且不需要地址索引(-A none)。-N4是多余的,因为我们只取4个字节,但也没有影响。我希望这样解释清楚了... - MLP

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接