我正在学习 shell 脚本,以方便我在计算机上的日常工作。
在Python中,有EAFP风格(“请求宽恕比请求许可更容易”)来处理异常。
>>> while True:
... try:
... x = int(input("Please enter a number: "))
... break
... except ValueError:
... print("Oops! That was no valid number. Try again...")
shell脚本中有这种风格吗?
答案1
这取决于你如何解释 EAFP。从狭义上讲,它指的是 Python 设计的异常处理的使用,但在某些语言中这是不可能的,而在大多数其他语言中则不常见且强烈不鼓励。然而,从更广泛的意义上来说,EAFP 在几乎所有编程语言中都很常见,尤其是在 shell 脚本中。
我假设POSIX shell 脚本,稍微关注重击。 Python 代码示例是 Python 3。
请求宽恕而不是许可的方法不止一种......
Python 是与二分法关系最密切的编程语言三思而后行 (LBYL)与请求宽恕比请求许可更容易 (EAFP)。在 Python 中,EAFP 通常意味着允许引发异常并捕获它,而不是在尝试操作之前检查操作是否会成功,这就是 LBYL 方式。
但这并不是唯一的两种可能性;有时,可以尝试执行失败时不引发异常的操作,但在尝试后检查它是否失败,并根据结果采取不同的操作。它不使用异常处理,因此从 Python 的角度来看,将其称为 EAFP 感觉很奇怪……但实际上,它与 EAFP 的共同点比 LBYL 更多。这种“EAFP”在 shell 脚本编写中非常常见。
我认为从 Python 示例开始是有帮助的,不仅因为这些术语与 Python 文化密切相关,还因为 EAFP、LBYL 和例外的“EAFP”都可以在 Python 中以干净简单的方式进行演示。假设d
类型为dict
:
# EAFP
try:
print(d['foo'])
except KeyError:
print('Not found.')
# LBYL
if 'foo' in d:
print(d['foo'])
else:
print('Not found.')
# Exceptionless "EAFP"
v = d.get('foo')
if v is None:
print('Not found.')
else:
print(v)
检查 猜测如果 shell 命令成功:LBYL
有时我们在 shell 脚本中使用简单的 LBYL。该代码包含在 Debian 和 Ubuntu 的默认.bashrc
文件中,用于检查主目录是否包含名为.bash_aliases
.如果没有,则不会尝试使用它。如果是这样,则会尝试获取它(在当前 shell 中将其每一行作为命令运行)。尽管这受到竞争条件,在这种情况下导致的错误不会造成损害,因此这是一个非常好的设计选择:
if [ -f ~/.bash_aliases ]; then
. ~/.bash_aliases
fi
但是,假设您想要进入子目录并执行您不想在子目录之外意外执行的操作。 LBYL,至少其纯粹形式,是一个糟糕的选择。在本例中,操作是从与一个匹配的多个子目录开始递归删除。全局。 (...这有点人为,因为有许多其他方法可以以合理的安全性完成它。如果您愿意,您可以想象在运行之前和/或之后该目录中有更多事情要做rm
。 )
# Don't do it this way!
if [ -d drafts ] && [ -x drafts ]; then # BAD!
cd drafts # BAD!
rm -r foo* # BAD!
fi # BAD!
在这里,我尝试检查更改目录是否drafts
会成功。我检查了它是否存在并且是一个目录 ( -d
) 以及我是否对其具有执行权限 ( -x
)。对于 *nix 系统上的目录,执行权限允许我对其进行更改。 (通常。)
除了这种方法相当复杂之外,问题还在于我可能错过了一些东西。即使什么都没有改变,这真的吗?保证我可以进入该目录吗?我检查过一切吗?[我将此作为读者的练习。]
另外,即使我有检查了所有内容并确保该目录存在并且可以访问,嗯......曾是。在这种情况下,竞争条件实际上很重要。如果发生变化,我无法进入该目录,并且命令cd
失败,那么我将删除所有foo*
并存的珍贵子目录,drafts
而不是其中包含的垃圾子目录!
运行 shell 命令并查看是否有效做过成功:EAFP?
您可能会注意到这些考虑因素和一些原因在 Python 中使用 EAFP:避免使用复杂或难以阅读的代码和情况可能发生变化的风险之间检查并根据检查尝试操作。事实上,这比上面所示的糟糕方法更简单、更健壮:
if cd drafts; then
rm -r foo*
fi
您可能会看到这样写,特别是如果希望在条件 ( cd drafts
) 失败时整个事物具有失败退出状态:
cd drafts && rm -r foo*
这不使用任何与异常处理稍微相似的东西。但它在跳跃之前不会先看。它不会检查drafts
目录是否存在且可访问。它只是试图改变它。然后,尝试后rm
,它检查是否成功,如果没有成功则拒绝继续运行该命令。
如果 EAFP 指的是使用异常处理,那么这不是 EAFP。如果您对比较犹豫不决,我理解;毕竟,这种方法早在异常处理之前就已经存在,并且在 C 等语言中很常见。但它是出于一些相同的目标,并且适用于许多相同的情况。
当然,这实际上是比较典型在编程中请求宽恕而不是许可的形式。 Python 很少见,因为即使在完全常见的情况下,它也不会被阻止(实际上是被鼓励)引发异常。
那么陷阱呢?
我有点同意jas-的回答。尽管 shell 脚本中的陷阱实际上并不是处理错误(在命令失败的意义上)的主要方式,但它们在某种程度上与 Python 中的异常处理类似。
然而,shell 脚本中的陷阱和 Python 中的异常之间存在一些重要的实际差异:
- Python 程序始终使用异常。大多数 shell 脚本很少使用陷阱(如果有的话)。
- Python 异常通常是从当前执行线程内引发的(尽管情况并非总是如此)。相反,陷阱通常是异步触发的,主要用于处理异步事件,例如从不同进程或系统发送的信号(例如,当用户按下Ctrl+时发出的 SIGINT C),尽管它们不限于此类案例。
陷阱很少用于 shell 脚本中的控制流,并且在有其他选项的情况下也不常见用于错误处理。要运行命令并发现它是否有效,您可以检查它退出状态。如上所示,控制结构if
提供了一种方法来做到这一点(并且还有其他人)。
答案2
陷阱是 shell 脚本的错误处理功能