看起来 cron 生成了一个 shell,而该 shell 又生成了一个脚本;我怎样才能摆脱中间 shell?

看起来 cron 生成了一个 shell,而该 shell 又生成了一个脚本;我怎样才能摆脱中间 shell?

操作系统是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类的话,请不要感到惊讶。shsh


分析

当时间到了你的 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 可以优化此步骤,但您的shshell 显然不能。有些 shell 可以。shell 可以可靠地判断它是否可以“消失”。我将在下面详细说明。


解决方案

  1. (较差)异步运行命令。如果你的命令是

    /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 会进行这种“优化”。

  2. (上级)明确execscript.sh

    exec /root/script.sh > /dev/null 2>&1
    

    exec使 shell 用命令替换自身而不创建新进程。在这种情况下,script.sh将在相同的进程 ID 下进行替换sh。在上述副作用的背景下,这非常好:

    • 父进程不会注意到sh“消失”的时间。它会看到script.sh而不是sh;并且会考虑script.sh子进程。如果没有exec父进程,则当其子进程 ( ) 在退出sh后立即退出时,父进程会收到通知。如果有父进程,则在退出时会收到通知,因为这是子进程。实际上,在两种情况下,都是在退出时。script.shexecscript.shscript.sh
    • 父进程终止时将直接从 获得退出状态script.sh。不再存在的事实sh无关紧要。如果没有父进程,它将从它运行的最后一个命令(即 )中exec获取由 传递的退出状态。实际上,在这两种情况下,它都是 的退出状态。shscript.shscript.sh

    这种方法几乎是透明的,即使父进程关心子进程何时退出或返回什么退出状态。这是 shell 可以做的优化。Bash 就是这样做的。在某些情况下,如果你不知道这一点,可能会感到很困惑;比较我的这个答案那里有sshd而不是 cron。

    如果你告诉 cron 使用bash而您不使用,exec则优化可能会启动,就像您使用 一样exec;也可能不会启动,这取决于命令。因此,如果您希望 shellexec确实执行 ,则明确告诉它exec;不要依赖 shell 的优化功能。

    现在您的 cron 用途sh很可能永远不会像这样优化,因此请exec在命令中明确使用。

    附注:exec command1; command2exec command1 && command2永远不会到达command2。请记住这一点,特别是如果您碰巧拥有exec command1并决定添加command2到工作中。这些应该可以工作:command1; exec command2command1 && exec command2。但是,然后期望sh进程在运行时存在;您需要它稍后command1运行。command2

相关内容