我正在研究脚本在从Debian存档(.deb)文件解压缩该程序包之前执行的 preinst 文件的内容。
该脚本具有以下代码:
#!/bin/bash
set -e
# Automatically added by dh_installinit
if [ "$1" = install ]; then
if [ -d /usr/share/MyApplicationName ]; then
echo "MyApplicationName is just installed"
return 1
fi
rm -Rf $HOME/.config/nautilus-actions/nautilus-actions.conf
rm -Rf $HOME/.local/share/file-manager/actions/*
fi
# End automatically added section
我的第一个问题是关于这一行:
set -e
我认为脚本的其余部分非常简单:它检查Debian / Ubuntu包管理器是否正在执行安装操作。如果是,它会检查我的应用程序是否刚刚安装在系统上。如果有,脚本将打印消息" MyApplicationName刚刚安装" 并结束(return 1
意味着以“错误”结束,不是吗?)
如果用户要求Debian / Ubuntu软件包系统安装我的软件包,该脚本还会删除两个目录。
这是对的还是我错过了什么?
答案 0 :(得分:615)
来自help set
:
-e Exit immediately if a command exits with a non-zero status.
但有些人认为这是不好的做法(bash FAQ和irc freenode #bash FAQ作者)。建议使用:
trap 'do_something' ERR
在发生错误时运行do_something
函数。
答案 1 :(得分:76)
set -e
将停止执行脚本 - 这与默认shell行为相反,即忽略脚本中的错误。在终端中键入help set
以查看此内置命令的文档。
答案 2 :(得分:43)
根据bash - The Set Builtin手册,如果设置了-e
/ errexit
,如果pipeline由一个simple command组成{{3},那么shell会立即退出}或a list返回非零状态。
默认情况下,管道的退出状态是管道中最后一个命令的退出状态,除非启用了pipefail
选项(默认情况下禁用它)。
如果是这样,管道的最后(最右边)命令的返回状态以非零状态退出,如果所有命令都成功退出,则返回零。
如果您想在退出时执行某些操作,请尝试定义trap
,例如:
trap onexit EXIT
其中onexit
是您在退出时执行某项操作的功能,例如下面打印简单的a compound command:
onexit(){ while caller $((n++)); do :; done; }
有类似的选项stack trace代替ERR,例如:
trap onerr ERR
零状态示例:
$ true; echo $?
0
非零状态示例:
$ false; echo $?
1
否定状态示例:
$ ! false; echo $?
0
$ false || true; echo $?
0
禁用pipefail
进行测试:
$ bash -c 'set +o pipefail -e; true | true | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; false | false | true; echo success'; echo $?
success
0
$ bash -c 'set +o pipefail -e; true | true | false; echo success'; echo $?
1
启用pipefail
进行测试:
$ bash -c 'set -o pipefail -e; true | false | true; echo success'; echo $?
1
答案 3 :(得分:42)
我在谷歌搜索时发现了这个问题,试图找出因set -e
而中止的脚本的退出状态。答案对我来说并不明显;因此这个答案。基本上, set -e
中止命令的执行(例如shell脚本)并返回失败命令的退出状态代码(即内部脚本,而不是外部脚本)。 / p>
例如,假设我有一个shell脚本outer-test.sh
:
#!/bin/sh
set -e
./inner-test.sh
exit 62;
inner-test.sh
的代码是:
#!/bin/sh
exit 26;
当我从命令行运行outer-script.sh
时,我的外部脚本以内部脚本的退出代码终止:
$ ./outer-test.sh
$ echo $?
26
答案 4 :(得分:5)
我相信这样做的目的是使相关脚本快速失败。
要自己进行测试,只需在bash提示符下键入<select>
<option value="always">Always</option>
<option value="tagged">Only when tagged</option>
<option value="never">Never</option>
</select>
。现在,尝试运行set -e
。您会得到一个目录清单。现在,键入ls
。该命令无法识别,并且将返回错误代码,因此您的bash提示将关闭(由于lsd
)。
现在,要在“脚本”的上下文中了解此内容,请使用以下简单脚本:
set -e
如果按原样运行它,则会在最后一行的#!/bin/bash
# set -e
lsd
ls
中获得目录列表。如果取消注释ls
并再次运行,则目录列表将不会显示,因为bash遇到来自set -e
的错误后将停止处理。
答案 5 :(得分:3)
这是一个古老的问题,但是这里没有答案讨论在Debian软件包处理脚本中使用set -e
或set -o errexit
的问题。根据Debian策略,在这些脚本中必须使用强制选项。显然是为了避免发生未处理的错误情况。
实际上,这意味着您必须了解在什么条件下运行的命令可能会返回错误,并明确地处理每个错误。
常见陷阱是diff
(存在差异时返回错误)和grep
(没有匹配项时返回错误)。您可以通过显式处理避免错误:
diff this that ||
echo "$0: there was a difference" >&2
grep cat food ||
echo "$0: no cat in the food" >&2
(还要注意我们如何注意在消息中包含当前脚本的名称,并将诊断消息写入标准错误而不是标准输出。)
如果确实没有必要进行显式处理,则不进行任何操作:
diff this that || true
grep cat food || :
(shell的:
no-op命令的使用有些晦涩,但很常见。)
只需重申
something || other
是
的简写if something; then
: nothing
else
other
fi
即我们明确表示other
仅当something
失败时才应运行。常用的if
(以及其他while
,until
之类的shell流控制语句)也是处理错误(实际上,如果不是{{ 1}}永远不能包含流控制语句!)
而且,为了明确起见,如果没有这样的处理程序,set -e
会导致整个脚本立即失败,并且如果set -e
发现差异,或者如果{{ 1}}找不到匹配项。
另一方面,某些命令在您需要时不会产生错误退出状态。常见的有问题的命令是diff
(退出状态不反映是否实际找到文件)和grep
(退出状态不会显示脚本是否接收到任何输入或实际上是否成功执行了任何命令)。在某些情况下,一种简单的防护措施是通过管道传递到没有输出的情况下发出尖叫的命令:
find
应注意,管道的退出状态是该管道中最后一条命令的退出状态。因此,以上命令实际上完全掩盖了sed
和find things | grep .
sed -e 's/o/me/' stuff | grep ^
的状态,仅告诉您find
是否最终成功。
(Bash当然具有sed
;但是Debian软件包脚本不能使用Bash功能。该策略明确要求这些脚本使用POSIX grep
,尽管并非总是如此。 )
在许多情况下,防御性地进行编码时需要格外注意。有时你必须检查一个临时文件,这样您就可以查看产生该输出的命令是否成功完成,即使是惯用语和便利性也会使您直接使用Shell管道。
答案 6 :(得分:2)
Script 1: without setting -e
#!/bin/bash
decho "hi"
echo "hello"
This will throw error in decho and program continuous to next line
Script 2: With setting -e
#!/bin/bash
set -e
decho "hi"
echo "hello"
# Up to decho "hi" shell will process and program exit, it will not proceed further
答案 7 :(得分:0)
cat a.sh
#! /bin/bash
#going forward report subshell or command exit value if errors
#set -e
(cat b.txt)
echo "hi"
./a.sh; echo $?
cat: b.txt: No such file or directory
hi
0
使用set -e注释掉,我们看到正在报告回显“ hi”退出状态,并且打印了hi。
cat a.sh
#! /bin/bash
#going forward report subshell or command exit value if errors
set -e
(cat b.txt)
echo "hi"
./a.sh; echo $?
cat: b.txt: No such file or directory
1
现在,我们看到的是报告的b.txt错误,并且没有打印出来。
因此,shell脚本的默认行为是忽略命令错误并继续处理并报告上一个命令的退出状态。如果要退出错误并报告其状态,可以使用-e选项。