问题描述
我有以下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
。 /bin
在rc.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
运行。由于它确实运行,/bin
在PATH
中,这意味着您可以运行位于/bin
中的其他命令,而无需完全限定其名称。例如,您可以运行chmod
而不是/bin/chmod
。但是,为了与您在rc.local
中的风格保持一致,我建议您运行它们时,除了sh
之外,我使用了所有命令的完全限定名称。)
我们可以非常肯定/bin/chmod +x /script.sh
从未运行(或者您会看到/script.sh
已执行)。而且我们知道sh /script.sh
也没有运行。
但它下载了/script.sh
。它成功了!怎么会失败?
成功的两个含义
当一个人说命令成功时,一个人可能会有两种不同的意思:
-
它做了你想做的事。
-
它报告说它成功了。
所以它是失败的。当一个人说命令失败时,它可能意味着:
-
它没有做你想做的事。
-
据报道它失败了。
与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”)进行测试。但是,上面的示例应该足以解释什么是测试,并使您可以有效地使用文档来确定特定的语句/命令是否是测试。
sh
与sh -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
)运行的脚本继续运行,直到它们到达终止(退出)它们的命令,或者到脚本的最后。每个命令的成功或失败基本上是无关紧要的(除非下一个命令检查它)。脚本以最后一个命令运行的退出状态退出。
帮助你的脚本理解它毕竟不是这样的失败
如果没有,你怎么能让你的脚本认为它失败了呢?
你看看它在运行之前发生了什么。
-
如果命令在应该成功时失败,找出原因并解决问题。
-
如果命令失败,并且这是正确的事情,那么防止该失败状态传播。
-
保持故障状态不传播的一种方法是运行另一个成功的命令。
/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脚本中:
-
0
的返回值表示成功和/或为真。 -
除
0
之外的其他值的返回值表示失败和/或错误。
-
-
在C编程中:
-
0
的返回值表示错误.. -
除
0
之外的其他值的返回值表示true。 -
什么意味着成功,什么意味着失败,没有简单的规则。有时
0
表示成功,有时则表示失败,有时则表示刚添加的两个数字之和为零。通用编程中的返回值用于表示各种不同类型的信息。 -
程序的数字退出状态应根据shell脚本规则指示成功或失败。也就是说,即使
0
在您的C程序中表示为false,如果您希望它成功报告(然后shell将其解释为true),您仍然会使程序返回0
作为其退出代码。
-
参考资料