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

我有以下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 

第一行, startup_script.sh实际上将script.sh文件下载到第三行中引用的/script.sh

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

您可以一直跳到A Quick Fix,但这不一定是最佳选择。 所以我建议先阅读所有这些内容。

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很少失败。 如果它运行的文件是只读的,它将失败,如果它所在的文件系统不支持权限,则可能会失败。 这两者都不可能。

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

什么失败了?

sh /home/incero/startup_script.sh失败了。 几乎可以确定。

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

(否则,确保它确实运行是很重要的,如果某种方式/bin不在PATH中 – rc.local不一定具有与登录时相同的PATH 。如果/bin不是在rc.local的路径中,这需要sh作为/bin/sh运行。由于它确实运行, /binPATH ,这意味着你可以运行位于/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运行的脚本(如rc.local )将在命令第一次报告失败时停止运行。 命令实际上做了什么并没有什么不同。

除非你打算让startup_script.sh在你想要的时候报告失败,否则这是startup_script.sh一个错误。

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

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

如何报告成功或失败

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

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

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

例如,如果C程序以exit(0);结束exit(0); ,或return 0;main()函数中,代码0被赋予操作系统,操作系统将其提供给调用进程(例如,可以是运行程序的shell)。

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

命令意味着失败

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

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

测试意味着失败

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

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

 if [ 4 -eq 5 ]; then echo "Yeah, they're totally the same." fi 

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

你看,在shell脚本中,成功也意味着真实 ,失败也可能意味着错误

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

但是,假设我写的更短:

 [ 4 -eq 5 ] && echo "Yeah, they're totally the same." 

这是常见的简写。 &&是一个布尔运算符。 除非双方都返回true(成功),否则&&表达式(由任何一方的&& with语句组成)将返回false(失败)。 就像一个正常的

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

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

这里, [ 4 -eq 5 ]运行。 它“失败”(返回false)。 所以整个&&表达失败了。 echo "Yeah, they're totally the same." 永远不会跑。 一切都表现得应该如此,但是这个命令会报告失败 (即使在上面有条件的情况下报告成功)。

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

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

sh vs. sh -e ,重访

#!/etc/rc.local顶部的行 (也见这个问题 )有sh -e ,操作系统运行脚本就好像用命令调用它一样:

 sh -e /etc/rc.local 

相反,您的其他脚本(例如startup_script.sh )在没有 -e标志的情况下运行:

 sh /home/incero/startup_script.sh 

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

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

只要确保记住差异:

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

    就好像脚本是一个单一的长命令,包含脚本中与&&运算符连接的所有命令。

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

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

如果没有,你怎么能让你的脚本认为它失败了呢?

你看看它在运行之前发生了什么。

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

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

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

    • 另一个是确保脚本从exit 0终止。

      这与脚本末尾的exit 0不一定相同。 例如,可能存在脚本退出的if块。

在报告成功之前,最好先了解导致脚本报告失败的原因。 如果它真的以某种方式失败(在没有做你想做的事情的意义上),你真的不希望它报告成功。

快速修复

如果无法使startup_script.sh退出报告成功,则可以更改运行它的rc.local中的命令,以便即使startup_script.sh没有,命令也会报告成功。

目前你有:

 sh /home/incero/startup_script.sh 

此命令具有相同的副作用(即运行startup_script.sh的副作用),但始终报告成功:

 sh /home/incero/startup_script.sh || /bin/true 

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

快速修复如何工作

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

假设你问我是拿出垃圾还是刷了猫头鹰。 如果我拿出垃圾,我可以如实地说“是的”,即使我不记得是否刷了猫头鹰。

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

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

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

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

随意忽略这一点。 如果您使用多种语言编程,则可能需要阅读此内容(例如,不仅仅是shell脚本)。

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

  • 在shell脚本中:

    1. 返回值0表示成功和/或为
    2. 0以外的返回值表示失败和/或错误
  • 在C编程中:

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

您可以(在我使用的Unix变体中)通过更改以下内容来覆盖默认行为:

 #!/bin/sh -e 

至:

 #!/bin/sh 

在脚本的开头。 “-e”标志指示sh在第一个错误时退出。 该标志的长名称是“errexit”,因此原始行等同于:

 #!/bin/sh --errexit 

改变第一行: #!/bin/sh -e!/bin/sh -e