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


35

我有以下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不能在启动时运行吗?

Answers:


70

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

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不在rc.local的路径中,这将需要sh以as身份运行/bin/sh。由于它确实/bin在in中运行,PATH这意味着您可以运行位于中的其他命令/bin,而无需完全限定其名称。例如,您可以运行chmod而不是/bin/chmod。在中rc.localsh无论何时建议运行它们,我都对所有命令使用完全限定的名称。)

我们可以确定/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停止运行之前,可能会在中发生这样的事情。在脚本中运行最后一条命令可能是报告失败(即使其“失败”可能完全正常甚至是必要的),这会使脚本报告失败。

测试意味着失败

一种特殊的命令是test,它的意思是命令针对返回值而不是副作用运行。也就是说,测试是运行的命令,因此可以检查其退出状态(并对其执行操作)。

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

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

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

您会看到,在shell脚本中,成功也可能意味着true,而失败也可能意味着false

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

但是,假设我将其写得更短:

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

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

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

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

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

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

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

shvs. sh -e,再访

由于has 顶部#!(另请参阅此问题),操作系统将运行脚本,就像使用以下命令调用该脚本一样:/etc/rc.localsh -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

此命令具有相同的副作用(即running的副作用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表示成功和/或true
    2. 返回值以外的其他值0表示失败和/或false
  • 在C编程中:

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

3
哇,好答案!那里有很多我不知道的信息。在第一个脚本的末尾返回0将导致其余脚本执行(嗯,我可以说chmod + x已运行)。不幸的是,我脚本中的/ usr / bin / wget似乎无法检索要放入script.sh的网页。我猜到该rc.local脚本执行时网络尚未就绪。我在哪里可以放置此命令,以便它在网络启动时运行,并在启动时自动运行?
Programster

我现在通过运行sudo visudo,添加以下行来“黑客入侵”它:username ALL =(ALL)NOPASSWD:所有正在运行“启动应用程序”程序(CLI命令是什么?),并向其中添加startup_script,现在,startupscript现在可以使用sudo命令预先运行script.sh,以root身份运行(不再需要密码)。毫无疑问,这是超级不安全且可怕的,但这仅用于自定义pxe-boot实时发行光盘。
Programster

@ Stu2000假设incero(我假设这是用户名)无论如何都是管理员,因此被允许以root(通过输入自己的用户密码)运行任何命令的方式运行,这并不是超级不安全和可怕的。如果您需要其他解决方案,建议您发布有关此问题的新问题。一个问题是为什么其中的命令rc.local没有运行(并且您还检查了使它们继续运行会发生什么情况)。现在,您遇到了另一个问题:您希望在rc.local运行之前使计算机与网络连接,或者安排startup_script.sh稍后运行。
伊利亚·卡根

1
从那以后,我回到了这个问题,编辑了rc.local,发现wget不能正常工作。/usr/bin/sudo service networking restart在wget命令之前添加到启动脚本中会导致wget然后可以工作。
Programster

3
@EliahKagan精彩而详尽的答案。我几乎没有遇到过更好的文档rc.local
souravc 2014年

1

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

#!/bin/sh -e

至:

#!/bin/sh

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

#!/bin/sh --errexit

是的,删除-e对覆盆子杰西的作品。
Oki Erie Rinaldi

-e是有原因的。如果我是你,我不会那样做。但我不是你的父母。
Wyatt8740

-4

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


3
的语法#!是正确的。(至少,该#!部分是正确的。其余部分通常都可以正常运行rc.local。)请参阅本文此问题无论如何,欢迎问Ubuntu!
伊利亚·卡根
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.