操作系统是CentOS 7。
我对 crontab 和 crond 有相当基本的了解。我使用以下命令创建了一个 crontab 条目crontab -e
:
*/5 * * * * /root/script.sh > /dev/null 2>&1
该脚本会检测是否使用文件多次运行.pid
并退出,因此在任何给定时间只有一个实例会继续运行。如果没有发生错误,该脚本将永远运行(直到关机/重启)。启动时,cron 将调用该脚本。
我无法理解的是产生了两个进程。这不是 crond 运行两次的情况。命令行不同。一个以脚本本身开始,sh
另一个以脚本本身开始。它们启动后,我可以终止进程sh
而不会产生任何不利影响,但如果我不终止它,它似乎会在脚本运行时一直运行。如果一个进程足以完成工作,我想避免连续运行两个进程。我做错了什么?
其他人肯定也遇到过这个问题,但我不知道如何进行谷歌搜索。有人能给我指明正确的方向吗?
答案1
我究竟做错了什么?
没什么。你做的并没有错,最多只是次优的。所描述的情况很正常。但它可以优化。继续阅读。
初步说明
cron 和 有不同的实现sh
。我想我可以运行一个临时 CentOS 7,并相当准确地从中推断出你的 cron 和你的。但我不会。这个答案会更通用。因此,如果我写类似“可以这样做,但显然你的不能”之sh
类的话,请不要感到惊讶。sh
sh
分析
当时间到了你的 cron 运行/bin/sh
(或另一个壳) 并将/root/script.sh > /dev/null 2>&1
字符串作为 的选项参数传递-c
。因此就像
/bin/sh -c '/root/script.sh > /dev/null 2>&1'
除了上面这一行是你在 shell 中输入的内容之外,cron 直接构建(或至少应该构建)参数数组,其中/bin/sh
,-c
和/root/script.sh > /dev/null 2>&1
是单独的元素,不需要额外的单引号(因此你可以在命令中使用单引号并且它们不会破坏任何东西)。
在将指定命令传递给 之前,cron 至少会对其sh
进行解释%
(请参阅man 5 crontab
)。您的命令不包含%
,因此此机制不相关。Cron 可以做更多:
- 它可以检测
$
、、|
等&&
,;
并决定它应该运行一个可以处理这些内容的 shell。 - 在您的情况下,它可以检测到不需要 shell,如果只有 cron 本身处理重定向;所以它会处理重定向并
/root/script.sh
直接运行。 - 它可以检测非常简单的命令(例如
/usr/bin/beep
)并直接运行它们。
或许可以,但是不应该。它的工作不是优化并像 shell 一样运行。它的工作是运行命令解释器 (shell),然后从这里获取它。
在你的情况下sh
是生成的。它解释/root/script.sh > /dev/null 2>&1
、处理重定向并/root/script.sh
作为子进程运行。
通常,shell 可以优化此步骤,但您的sh
shell 显然不能。有些 shell 可以。shell 可以可靠地判断它是否可以“消失”。我将在下面详细说明。
解决方案
(较差)异步运行命令。如果你的命令是
/root/script.sh > /dev/null 2>&1 &
然后 cron 将运行
sh
,并且sh
将退出而不等待script.sh
完成。但这有副作用:- cron 将看到其子进程(即
sh
)退出。 cron 和进程之间没有直接联系script.sh
(除非 cron 将自己设置为“子收割者”,你的 cron 可能没有)。 - cron 将看到其子进程(即
sh
)以退出状态退出0
。通常(没有&
)的退出状态sh
将取决于的退出状态script.sh
;但要发生这种情况sh
必须等待script.sh
完成。
通过手动终止,
sh
您会无意中引入这些副作用。特别是 cron 并不关心,我认为(虽然可以配置它来记录失败的作业)。但一般来说,父进程可能会关心,因此用户不应“随机”终止中间 shell;并且 shell 不应任意决定它们可以在不等待最后一个命令的情况下退出。据我所知,没有 shell 会进行这种“优化”。- cron 将看到其子进程(即
(上级)明确
exec
地script.sh
:exec /root/script.sh > /dev/null 2>&1
exec
使 shell 用命令替换自身而不创建新进程。在这种情况下,script.sh
将在相同的进程 ID 下进行替换sh
。在上述副作用的背景下,这非常好:- 父进程不会注意到
sh
“消失”的时间。它会看到script.sh
而不是sh
;并且会考虑script.sh
子进程。如果没有exec
父进程,则当其子进程 ( ) 在退出sh
后立即退出时,父进程会收到通知。如果有父进程,则在退出时会收到通知,因为这是子进程。实际上,在两种情况下,都是在退出时。script.sh
exec
script.sh
script.sh
- 父进程终止时将直接从 获得退出状态
script.sh
。不再存在的事实sh
无关紧要。如果没有父进程,它将从它运行的最后一个命令(即 )中exec
获取由 传递的退出状态。实际上,在这两种情况下,它都是 的退出状态。sh
script.sh
script.sh
这种方法几乎是透明的,即使父进程关心子进程何时退出或返回什么退出状态。这是 shell 可以做的优化。Bash 就是这样做的。在某些情况下,如果你不知道这一点,可能会感到很困惑;比较我的这个答案那里有
sshd
而不是 cron。如果你告诉 cron 使用
bash
而您不使用,exec
则优化可能会启动,就像您使用 一样exec
;也可能不会启动,这取决于命令。因此,如果您希望 shellexec
确实执行 ,则明确告诉它exec
;不要依赖 shell 的优化功能。现在您的 cron 用途
sh
很可能永远不会像这样优化,因此请exec
在命令中明确使用。附注:
exec command1; command2
或exec command1 && command2
永远不会到达command2
。请记住这一点,特别是如果您碰巧拥有exec command1
并决定添加command2
到工作中。这些应该可以工作:command1; exec command2
或command1 && exec command2
。但是,然后期望sh
进程在运行时存在;您需要它稍后command1
运行。command2
- 父进程不会注意到