为什么 rc.local 不运行我的所有命令,我该怎么办?

Pro*_*ter 36 startup scripts execute-command

我有以下rc.local脚本:

#!/bin/sh -e
#
# rc.local
#
# This script is executed at the end of each multiuser runlevel.
# Make sure that the script will "exit 0" on success or any other
# value on error.
#
# In order to enable or disable this script just change the execution
# bits.
#
# By default this script does nothing.

sh /home/incero/startup_script.sh
/bin/chmod +x /script.sh
sh /script.sh

exit 0
Run Code Online (Sandbox Code Playgroud)

第一行,startup_script.sh实际下载第三行中提到的script.sh文件/script.sh

不幸的是,它似乎没有使脚本可执行或运行脚本。rc.local启动后手动运行文件效果很好。chmod 不能在启动时运行吗?

Eli*_*gan 72

您可以一直跳到快速修复,但这不一定是最佳选择。所以我建议先阅读所有这些。

rc.local 不容忍错误。

rc.local不提供从错误中智能恢复的方法。如果任何命令失败,它将停止运行。第一行 ,#!/bin/sh -e使其在使用该-e标志调用的 shell 中执行。该-e标志是使脚本(在本例中为rc.local)在第一次命令失败时停止运行的原因。

你想rc.local表现得像这样。如果命令失败,您希望它继续执行任何其他可能依赖它成功的启动命令。

因此,如果任何命令失败,后续命令将不会运行。这里的问题是/script.sh没有运行(不是它失败,见下文),所以很可能是在它失败之前的一些命令。但哪一个?

/bin/chmod +x /script.sh吗?

不。

chmod任何时候都运行良好。如果包含的文件系统/bin已挂载,您就可以运行/bin/chmod. 并/binrc.local运行前安装。

以 root 身份运行时,/bin/chmod很少会失败。如果它操作的文件是只读的,它将失败,如果它所在的文件系统不支持权限,它可能会失败。两者都不可能在这里。

顺便说一句,这是如果失败实际上会成为问题sh -e唯一原因chmod。当您通过显式调用其解释器来运行脚本文件时,该文件是否标记为可执行文件并不重要。只有当它说/script.sh文件的可执行位才重要。既然它说sh /script.sh,它就不会(当然,除非在运行时/script.sh 调用自己,否则可能会因为它不可执行而失败,但它不太可能调用自己)。

那么失败了怎么办?

sh /home/incero/startup_script.sh失败的。几乎肯定。

我们知道它运行了,因为它下载了/script.sh.

(否则,确保它确实运行是很重要的,以防不知何故/bin不在PATH 中-rc.local不一定与PATH您登录时的相同。如果/bin不在PATH 中rc.local,则此将需要sh作为 运行/bin/sh。因为它确实运行了,/bin所以在 中PATH,这意味着您可以运行位于 中的其他命令/bin,而无需完全限定它们的名称。例如,您可以只运行chmod而不是/bin/chmod。但是,与您的风格保持一致在 中rc.local,我对所有命令都使用了完全限定名称,除了sh,每当我建议您运行它们时。)

我们可以肯定/bin/chmod +x /script.sh从未运行过(或者你会看到它/script.sh被执行了)。我们知道sh /script.sh也没有运行。

但它下载了/script.sh。它成功了!怎么会失败?

成功的两种含义

当一个人说命令成功时,他/她可能意味着两种不同的意思:

  1. 它做了你想让它做的事。
  2. 它报告说它成功了。

失败也是如此。当一个人说命令失败时,这可能意味着:

  1. 它没有做你想让它做的事情。
  2. 它报告说它失败了。

使用sh -e, like运行的脚本rc.local将在第一次报告失败的命令时停止运行。命令实际执行的操作没有区别。

除非您打算startup_script.sh在执行您想要的操作时报告失败,否则这是startup_script.sh.

  • 一些错误会阻止脚本执行您希望它执行的操作。它们会影响程序员所说的副作用
  • 并且一些错误会阻止脚本正确报告它是否成功。它们影响程序员称之为返回值的内容(在这种情况下是退出状态)。

最有可能的是,它startup_script.sh做了它应该做的一切,除了报告它失败了。

如何报告成功或失败

脚本是零个或多个命令的列表。每个命令都有一个退出状态。假设实际运行脚本没有失败(例如,如果解释器在运行时无法读取脚本的下一行),则脚本的退出状态为:

  • 0 (成功)如果脚本是空白的(即没有命令)。
  • N, 如果脚本因命令而结束,则某些退出代码在哪里。exit NN
  • 在脚本中运行的最后一个命令的退出代码,否则。

当一个可执行文件运行时,它会报告它自己的退出代码——它们不仅仅用于脚本。(从技术上讲,脚本的退出代码是运行它们的 shell 返回的退出代码。)

例如,如果一个 C 程序以exit(0);return 0;在其main()函数中结束,则代码0将提供给操作系统,操作系统将其提供给调用进程(例如,它可能是运行程序的外壳程序)。

0表示程序成功。每隔一个数字就意味着它失败了。(这样,不同的数字有时可以表示程序失败的不同原因。)

注定要失败的命令

有时,您运行程序的意图是它会失败。在这些情况下,您可能会将其失败视为成功,即使程序报告失败并不是错误。例如,您可能会rm在您怀疑已经不存在的文件上使用,只是为了确保它已被删除。

startup_script.sh就在它停止运行之前,可能正在发生这样的事情。在脚本中运行最后一个命令可能是报告失败(即使它的“失败”可能完全没有问题,甚至是必要的),这使得脚本报告失败。

注定要失败的测试

一种特殊的命令是test,我的意思是一个命令是为了它的返回值而不是它的副作用而运行的。也就是说,测试是一个运行的命令,以便可以检查(并对其采取行动)其退出状态。

例如,假设我忘记了 4 是否等于 5。幸运的是,我知道 shell 脚本:

if [ 4 -eq 5 ]; then
    echo "Yeah, they're totally the same."
fi
Run Code Online (Sandbox Code Playgroud)

在这里,测试[ -eq 5 ]失败,因为结果是 4 ? 5 毕竟。这并不意味着测试没有正确执行;它做了。它的工作是检查是否 4 = 5,如果是,则报告成功,如果不是,则报告失败。

你看,在 shell 脚本中,成功也可能意味着true,失败也意味着false

即使echo语句从不运行,if块作为一个整体确实返回成功。

但是,假设我写得更短:

[ 4 -eq 5 ] && echo "Yeah, they're totally the same."
Run Code Online (Sandbox Code Playgroud)

这是常见的速记。&&是一个布尔值 运算符。一个&&表达式,它由&&两边有陈述,返回false(失败),除非双方返回true(成功)。就像一个正常的

如果有人问你,“德里克有没有去商场想过一只蝴蝶?” 而且你知道德里克没有去商场,你不必费心弄清楚他是否想到了一只蝴蝶。

同样,如果左边的命令&&失败 (false),则整个&&表达式立即失败 (false)。右侧的语句&&永远不会运行。

来,[ 4 -eq 5 ]跑。它“失败”(返回false)。所以整个&&表达式失败了。echo "Yeah, they're totally the same."从不运行。一切都按其应有的方式运行,但此命令报告失败(即使if上面的其他等效条件报告成功)。

如果这是脚本中的最后一条语句(并且脚本到达它,而不是在它之前的某个点终止),则整个脚本将报告failure

除此之外还有很多测试。例如,有使用||(“或”)的测试。但是,上面的示例应该足以解释什么是测试,并使您可以有效地使用文档来确定特定语句/命令是否是测试。

sh对比sh -e,重温

由于has顶部#!(另请参阅此问题),操作系统运行脚本就像它是用命令调用的一样:/etc/rc.localsh -e

sh -e /etc/rc.local
Run Code Online (Sandbox Code Playgroud)

相比之下,其它脚本,例如startup_script.sh,运行-e标志:

sh /home/incero/startup_script.sh
Run Code Online (Sandbox Code Playgroud)

因此,即使其中的命令报告失败,它们也会继续运行。

这很正常也很好。rc.local应该sh -e与大多数其他脚本一起调用- 包括由 -- 运行的大多数脚本rc.local不应该。

只要确保记住区别:

  • 脚本sh -e在第一次包含退出报告失败的命令时运行并报告失败。

    就好像脚本是一个单一的长命令,由脚本中的所有命令和&&运算符组成。

  • 使用sh(不使用-e)运行的脚本会继续运行,直到到达终止(退出)它们的命令,或者到达脚本的最后。每个命令的成功或失败本质上是无关的(除非下一个命令检查它)。脚本以上次命令运行的退出状态退出。

帮助你的脚本理解它毕竟不是这样的失败

你怎么能不让你的脚本在它没有失败的时候认为它失败了?

你看看在它运行完成之前会发生什么。

  1. 如果命令在本应成功时失败,请找出原因并解决问题。

  2. 如果命令失败,并且这是正确的事情发生,则防止该失败状态传播。

    • 防止失败状态传播的一种方法是运行另一个成功的命令。/bin/true没有副作用并报告成功(就像/bin/false什么都不做也失败一样)。

    • 另一个是确保脚本以exit 0.

      这不一定与exit 0脚本末尾相同。例如,可能有一个if-block 脚本在里面退出。

在让它报告成功之前,最好先了解是什么导致您的脚本报告失败。如果它确实以某种方式失败了(在没有做你想让它做的意义上),你真的不希望它报告成功。

快速修复

如果您不能使startup_script.sh退出报告成功,您可以更改rc.local运行它的命令,以便该命令即使startup_script.sh没有成功也报告成功。

目前你有:

sh /home/incero/startup_script.sh
Run Code Online (Sandbox Code Playgroud)

这个命令有同样的副作用(即运行的副作用startup_script.sh),但总是报告成功:

sh /home/incero/startup_script.sh || /bin/true
Run Code Online (Sandbox Code Playgroud)

请记住,最好知道为什么startup_script.sh报告失败并修复它。

快速修复的工作原理

这实际上是一个||测试、一个测试的例子。

假设你问我是倒垃圾还是刷猫头鹰。如果我把垃圾倒掉了,即使我不记得我有没有刷过猫头鹰,我也可以如实说“是”。

||运行左侧的命令。如果成功(真),则右侧不必运行。因此,如果startup_script.sh报告成功,则该true命令永远不会运行。

但是,如果startup_script.sh报告失败【我没有倒垃圾】,那么/bin/true 【如果我刷猫头鹰】的结果很重要。

/bin/true总是返回成功(或true,我们有时称之为)。因此,整个命令成功,并且rc.local可以运行下一个命令。

关于成功/失败、真/假、零/非零的附加说明。

随意忽略这一点。但是,如果您使用一种以上的语言编程(即,不仅仅是 shell 脚本),您可能想要阅读本文。

对于使用 C 等编程语言的 shell 脚本编写者和使用 shell 脚本编写的 C 程序员来说,发现:

  • 在 shell 脚本中:

    1. 返回值0mean成功和/或true
    2. 返回值不是0意味着失败和/或false
  • 在 C 编程中:

    1. 返回值0意味着false ..
    2. 其他东西的返回值0意味着true
    3. 什么意味着成功,什么意味着失败,没有简单的规则。有时0意味着成功,有时意味着失败,有时意味着您刚刚添加的两个数字之和为零。一般编程中的返回值用于表示各种不同类型的信息。
    4. 根据 shell 脚本的规则,程序的数字退出状态应该指示成功或失败。也就是说,即使0在您的 C 程序中意味着 false,0如果您希望它报告它成功(然后 shell 将其解释为true),您仍然可以让您的程序作为其退出代码返回。

  • 哇很好的答案!里面有很多我不知道的信息。在第一个脚本的末尾返回 0 会导致脚本的其余部分执行(我可以说 chmod +x 运行了)。不幸的是,我的脚本中的 /usr/bin/wget 似乎无法检索网页以放入 script.sh。我猜这个 rc.local 脚本执行时网络还没有准备好。我可以把这个命令放在哪里,让它在网络启动时运行,并在启动时自动运行? (3认同)
  • @EliahKagan 精彩详尽的答案。我几乎没有遇到过更好的关于 `rc.local` 的文档 (3认同)