我正在研究这个preinst文件的内容,该脚本在包从Debian归档文件(.deb)文件解压缩之前执行该文件。

脚本代码如下:

#!/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刚刚安装”并结束(返回1意味着以“错误”结束,不是吗?)

如果用户要求Debian/Ubuntu包系统安装我的包,脚本还会删除两个目录。

是这样吗,还是我漏掉了什么?


当前回答

如果命令或管道出现错误,Set -e将停止脚本的执行——这与默认shell行为相反,默认shell行为是忽略脚本中的错误。在终端中键入help set以查看此内置命令的文档。

其他回答

我相信这样做的目的是为了让这个剧本迅速失败。

要亲自测试,只需在bash提示符下键入set -e。现在,试着运行ls。您将得到一个目录列表。现在输入lsd。该命令无法识别,并将返回一个错误代码,因此bash提示符将关闭(由于set -e)。

现在,为了在“脚本”的上下文中理解这一点,使用这个简单的脚本:

#!/bin/bash 
# set -e

lsd 

ls

如果按原样运行,您将从最后一行的ls中获得目录列表。如果取消set -e的注释并再次运行,则不会看到目录列表,因为bash一旦遇到来自lsd的错误就会停止处理。

这是一个老问题,但这里没有一个答案讨论set -e的使用,即set -o errexit在Debian包处理脚本中。根据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应该运行当且仅当某些东西失败时。手动的if(以及其他shell流控制语句,如while, until)也是处理错误的有效方法(实际上,如果不是的话,带有set -e的shell脚本永远不可能包含流控制语句!)

而且,为了显式地说明,在没有这样的处理程序的情况下,如果diff发现差异,或者如果grep没有找到匹配,set -e将导致整个脚本立即失败并报错。

另一方面,有些命令不会在您希望它们产生错误退出状态时产生错误退出状态。常见的问题命令是find(退出状态不反映文件是否实际找到)和sed(退出状态不会显示脚本是否收到任何输入或实际成功执行任何命令)。在某些情况下,一个简单的守卫是管道到一个命令,如果没有输出就会尖叫:

find things | grep .
sed -e 's/o/me/' stuff | grep ^

应该注意的是,管道的退出状态是该管道中最后一条命令的退出状态。因此,上面的命令实际上完全掩盖了find和sed的状态,只告诉您grep最终是否成功。

(当然,Bash已经设置了-o pipefail;但是Debian包脚本不能使用Bash特性。该策略坚决要求这些脚本使用POSIX sh,尽管并非总是如此。)

在许多情况下,这是在进行防御性编码时需要单独注意的问题。有时你必须通过一个临时文件,这样你才能看到产生输出的命令是否成功完成,即使习惯用法和便利性会引导你使用shell管道。

如果命令失败,它将停止脚本的执行。

一个明显的例外是if语句。例如:

set -e
false
echo never executed
set -e
if false; then
  echo never executed
fi

echo executed

false

echo never executed

我在试图弄清楚由于set -e而中止的脚本的退出状态是什么时发现了这篇文章。答案对我来说并不明显;因此有了这个答案。基本上,set -e终止命令(例如shell脚本)的执行,并返回失败命令的退出状态码(即内部脚本,而不是外部脚本)。

例如,假设我有一个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
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被注释掉时,我们看到echo "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错误被报告,没有打印hi。

因此shell脚本的默认行为是忽略命令错误并继续处理并报告最后一个命令的退出状态。如果你想退出错误并报告它的状态,我们可以使用-e选项。