exec 应该始终是 shell 脚本的最后一行吗?

exec 应该始终是 shell 脚本的最后一行吗?

我有一个 shell 脚本,它执行以下操作:

export FOO=foo           # step 1
/usr/bin/java my-server  # step 2

该 shell 脚本由需要知道在步骤 2 中启动的进程的 PID 的父程序启动。目前,这是通过exec在服务器上运行 launch 命令以用步骤 2 中的命令替换 shell 来实现的。因此,该 shell 脚本的 PID运行脚本的进程成为“my-server”进程的 PID。

现在我必须向此 shell 脚本添加另一个命令,以快速验证步骤 2 中启动的服务器。因此我的程序现在应如下所示:

export FOO=foo                      # step 1
exec /usr/bin/java my-server &      # step 2
/usr/bin/java my-validation         # step 3

我可以添加如上所示的步骤 3 吗?这种方法有哪些替代方案?

答案1

结合其他答案和评论所说的内容,然后添加一点:

  • exec your_command &没有意义。命令行将在异步子 shell 中运行,并且exec基本上会被忽略。
    • 那是一个轻微简单化。如果我们说
      命令1;命令2) &
      then并将在异步子 shell 中 运行(完成后 )。但如果我们说command1command2command2command1
      执行 命令1;命令2) &
      然后会运行但不会。command1command2
  • 脚本中正确形成的 exec命令(不仅仅包含重定向)之后的任何内容都将被忽略。即使exec失败,shell 也会退出而不运行后续行(可以使用command exec ...orbashexecfail选项来防止exec失败时退出 shell)。
  • 如果exec在子 shell 中运行,例如在exec cmd &orexec cmd | cmd2或中(exec cmd3),则只有该子 shell 退出。
  • 如果您的验证命令需要知道服务器进程的 PID,那么您可能会不走运。
  • 否则,如果您的验证命令自动等待一段时间服务器启动,您可以这样做

    export FOO=foo                      # step 1
    /usr/bin/java my-validation &       # step 1½
    exec /usr/bin/java my-server        # step 2
    
  • 否则,你可以这样做

    export FOO=foo                              # step 1
    (sleep 10; /usr/bin/java my-validation) &   # step 1½
    exec /usr/bin/java my-server                # step 2
    

    警告:如果“my-server”进程快速终止,那么在“my-validation”命令运行之前,您的脚本很可能会退出,并且父进程将重新获得控制权并恢复运行。如果父进程和“my-validation”命令都将输出写入同一位置,则它们可能会混合。

    但如果父进程不等待脚本退出,这可能不是问题。

答案2

exec program用 new 替换脚本的进程program,因此应该仅在有意义的地方使用。也就是说,当前脚本将消失并且将无法运行任何后续命令(除非exec program失败)。

exec program因为 shell 脚本的最后一行在program长时间运行时非常有用,并且您不希望无用的 shell 进程在进程树中徘徊直到program退出。使用pstree或其他处理工具来检查以下之间的差异:

#!/bin/sh
echo $$
sleep 9999

#!/bin/sh
echo $$
exec sleep 9999

exec program如果您想在 后运行其他 shell 命令,则根本没有用exec,因为这些 shell 命令未达到(除非exec失败)。

在启动后验证某些内容实际上相当棘手,因为/usr/bin/java my-server启动需要时间(Java 在这方面可能非常慢;我记得一个 Java 进程需要 45 秒以上才能可用或自行关闭......)根据系统的繁忙程度或调度程序,验证过程可能会在my-server运行之前或之后运行。作为一个克鲁格,你可以用类似的东西来延迟验证步骤

export FOO=foo
/usr/bin/java my-server &
sleep 10
exec /usr/bin/java my-validation

它给出了 10 秒的时间来my-server启动自身,这可能足够也可能不够,具体取决于系统的繁忙程度以及my-server启动所需的时间。我再次发现 Java 进程需要 45 秒以上才能启动,而且在繁忙的系统上启动时间可能会更长。如果足够聪明,可以等待启动并运行,则可能sleep没有必要。my-validationmy-server

仅启动服务器可能更有意义:

#!/bin/sh
export FOO=foo
exec /usr/bin/java my-server

并验证服务正在运行通过监控框架,其他地方。

另一种方法是与systemd其他现代 init 框架集成,该框架提供比 shell 脚本更好的过程控制。例如,进程可以返回其systemd是否已正确启动的信息,或者systemd可以在各种条件下自动重新启动服务,等等。

相关内容