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

为什么 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 不能在启动时运行吗?

答案1

您可以直接跳至快速修复但这不一定是最好的选择。所以我建议先读完这些。

rc.local不容忍错误。

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

您希望rc.local这样做。如果命令失败,您可以不是希望它能够继续执行依赖于它成功的任何其他启动命令。

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

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

不。

chmod任何时候都可以正常运行。只要包含的文件系统/bin已挂载,您就可以运行/bin/chmod。并且已在运行/bin之前挂载rc.local

以 root 身份运行时,/bin/chmod很少会失败。如果它操作的文件是只读的,则会失败,并且可能如果文件系统不支持权限,则失败。这里不太可能出现这种情况。

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

那么什么失败了?

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

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

(否则,确保它确实运行就很重要,以防/bin万一小路--rc.local不一定PATH与您登录时拥有的相同。如果/bin不在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,例如rc.local,将在第一次命令时停止运行报告称失败。命令实际上执行的操作没有区别。

除非您打算startup_script.sh在它执行所需的操作时报告失败,否则这是一个错误startup_script.sh

  • 有些错误会阻止脚本执行您想要它执行的操作。它们会影响程序员所称的副作用
  • 有些错误会阻止脚本正确报告其是否成功。它们会影响程序员所称的返回值(在本例中是退出状态)。

它很可能已经startup_script.sh完成了它应该做的一切,除了报告失败了。

如何报告成功或失败

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

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

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

例如,如果 C 程序以 结尾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."

这是常见的简写。&&布尔值 运算符。&&表达式由两边的 with 语句组成&&,除非两边都返回 true(成功),否则返回 false(失败)。就像普通的

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

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

这里,[ 4 -eq 5 ]运行。它“失败”(返回 false)。因此整个&&表达式失败。echo "Yeah, they're totally the same."永远不会运行。一切都按预期运行,但此命令报告失败(即使if上面的其他等效条件报告成功)。

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

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

shsh -e,重温

自从线#!(也可以看看这个问题/etc/rc.local) 位于has的顶部sh -e,操作系统将运行该脚本,就像使用以下命令调用它一样:

sh -e /etc/rc.local

相反,您的其他脚本(例如startup_script.sh)运行没有旗帜-e

sh /home/incero/startup_script.sh

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

这是正常且良好的。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

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

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

记住,最好知道startup_script.sh报告失败的原因并修复它。

快速修复如何工作

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

假设你问我是否倒了垃圾或者是否梳理过猫头鹰的毛发。如果我​​倒了垃圾,我可以如实回答“是”,即使我不记得我是否梳理过猫头鹰的毛发。

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

但是,如果startup_script.sh报告失败[我没有倒垃圾],那么结果/bin/true [如果我刷了猫头鹰]事。

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

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

请忽略此内容。不过,如果您使用多种语言进行编程(即不仅仅是 shell 脚本),则可能需要阅读此内容。

对于使用 C 等编程语言的 Shell 脚本编写者以及使用 Shell 脚本的 C 程序员来说,他们非常困惑于发现:

  • 在 shell 脚本中:

    1. 返回值0意味着成功和/或真的
    2. 返回值不是0手段失败和/或错误的
  • 在 C 编程中:

    1. 返回值0意味着错误的..
    2. 返回值不是0手段真的
    3. 对于成功和失败,并没有简单的规则。有时0表示成功,有时表示失败,有时表示刚刚添加的两个数字的总和为零。在一般编程中,返回值用于表示各种不同类型的信息。
    4. 根据 shell 脚本的规则,程序的数字退出状态应指示成功或失败。也就是说,即使0C 程序中的 means 为 false,0如果希望程序报告成功(shell 随后将其解释为),仍应让程序返回其退出代码真的)。

答案2

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

#!/bin/sh -e

到:

#!/bin/sh

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

#!/bin/sh --errexit

答案3

@Bryce 的回答在某些情况下是完全没问题的——取决于你用它做什么rc.local。如果在第一次错误之后什么都不发生是绝对关键的,那当然-e就是你想要的。但它rc.local可以用于许多目的,而且你可以把一些事情放在其中,而这些事情并不依赖于其中其他事情的成功。

不过,我回答这个问题的主要原因是解释你可以做些什么来找出你的 rc.local 哪里出了问题。我强烈建议在你的 rc.local 脚本的开头写上类似这样的内容:

exec 2> /tmp/rc.local.log      # send stderr from rc.local to a log file
exec 1>&2                      # send stdout to the same log file
set -x                         # tell sh to display commands before execution

date                           # good practice to record when stuff happened

答案4

将第一行从:更改 #!/bin/sh -e!/bin/sh -e

相关内容