当前位置: 首页>>技术问答>>正文


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

顺便说一下,如果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中的错误。

  • 一些错误会阻止脚本执行您希望它执行的操作。它们影响程序员称之为side effects

  • 并且一些错误会阻止脚本正确报告它是否成功。它们影响程序员称之为return value(在本例中为exit status)。

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

如何报告成功或失败

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

  • 如果脚本为空(即没有命令),则为0(成功)。

  • N,如果脚本因命令exit N而结束,其中N是某些退出代码。

  • 否则,在脚本中运行的最后一个命令的退出代码。

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

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

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

命令意味着失败

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

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

测试意味着失败

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

例如,假设我忘了如果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."

这是常见的简写。 &&boolean和操作员。 &&表达式由&&和任何一方的语句组成,返回false(失败),除非双方都返回true(成功)。就像正常和。

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

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

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

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

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

shsh -e,再访

由于/etc/rc.local顶部的#!行(另请参阅解释器是否读取了#!/bin/sh?)具有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 -block,其中脚本退出。

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

快速修复

如果无法使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报告失败并修复它。

快速修复如何工作

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

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

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

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

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

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

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

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

  • 在shell脚本中:

    1. 0的返回值表示成功和/或为真。

    2. 0之外的其他值的返回值表示失败和/或错误。

  • 在C编程中:

    1. 0的返回值表示错误..

    2. 0之外的其他值的返回值表示true。

    3. 什么意味着成功,什么意味着失败,没有简单的规则。有时0表示成功,有时则表示失败,有时则表示刚添加的两个数字之和为零。通用编程中的返回值用于表示各种不同类型的信息。

    4. 程序的数字退出状态应根据shell脚本规则指示成功或失败。也就是说,即使0在您的C程序中表示为false,如果您希望它成功报告(然后shell将其解释为true),您仍然会使程序返回0作为其退出代码。

参考资料

本文由Ubuntu问答整理, 博文地址: https://ubuntuqa.com/article/2990.html,未经允许,请勿转载。