为什么在采购文件之前先检查文件是否存在?


13

尝试获取文件源时,是否会出现错误消息,指出该文件不存在,所以您知道要解决的问题?

例如,nvm建议将此添加到您的配置文件/ rc:

export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm

使用上面的命令,如果nvm.sh不存在,您将收到“静默错误”。但是,如果尝试. "$NVM_DIR/nvm.sh",输出将是FILE_PATH: No such file or directory


3
你不应该 这很讲究。尝试使用源代码,然后处理错误(如果有的话)
Mikel

2
@Mikel对!那我为什么到处都能看到它?
JBallin

3
@FaheemMitha,好吧,如果您所做的只是对安全性敏感的(即您的程序代表他人运行),那么您确实需要关心竞争条件(TOCTOU)。不过,这里可能不是这样,因为如果有人修改中的文件,则会遇到更大的问题HOME
ilkkachu

8
@FaheemMitha这是从来没有最好先检查它是否存在。这种情况在测试和使用之间可能会发生变化,同时产生误报和误报:而且无论如何,您仍然必须处理使用失败的情况。正如您提到的性能,使用前的测试速度是不使用它并让系统执行该操作的速度慢了两倍,无论如何它都会这样做。不可能不是。
user207421

1
@SimonRichter,在这种情况下,nvm无法正常工作。用户必须自己弄清楚该文件是否丢失。
JBallin

Answers:


25

在POSIX shell中,它.是一个特殊的内置函数,因此它的失败会导致shell退出(在某些shell中,例如bash,仅在POSIX模式下才完成)。

符合错误的条件取决于外壳。解析文件时,并非所有文件都因语法错误而退出,但是当找不到或打开源文件时,大多数文件都会退出。我不知道如果源文件中的最后一个命令返回的退出状态为非零(除非该errexit选项为开),否则会退出。

在这里做:

[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"

在这种情况下,如果文件存在,则要源文件;如果文件不存在,则不源文件(或在此处为空-s)。

也就是说,如果文件不存在,则不应将其视为错误(POSIX外壳中的致命错误),该文件将被视为可选文件。

如果文件不可读或为目录,或者(在某些外壳中)解析时仍然存在语法错误(仍然是应报告的实际错误情况),则仍然(致命)错误。

有人会争辩说存在比赛条件。但是,这唯一的意思是,如果在[和之间删除文件,则外壳程序将退出并显示错误.,但我认为将脚本修复为固定路径文件突然消失是错误的说法是正确的。运行。

另一方面,

command . "$NVM_DIR/nvm.sh" 2> /dev/null

其中command¹删除了命令的特殊属性.(因此它不会在出现错误时退出外壳)将无法正常工作:

  • 它会隐藏.的错误,还会隐藏在源文件中运行的命令的错误
  • 它还会隐藏真正的错误情况,例如文件权限错误。

其他常见语法(例如,grep -r /etc/default /etc/init*在Debian系统上,请参阅尚未转换为init脚本systemdEnvironmentFile=-/etc/default/service用于在其中指定可选的环境文件)的初始化脚本)包括:

  • [ -e "$file" ] && . "$file"

    检查文件是否存在,如果文件为空,则仍将其作为源。如果无法打开(即使它在那里,或者在那里),仍然是致命错误。您可能会看到更多的变体,例如[ -f "$file" ](存在并且是常规文件),[ -r "$file" ](可读)或这些形式的组合。

  • [ ! -e "$file" ] || . "$file"

    一个更好的版本。更清楚地表明该文件不存在是可以的。这也意味着$?它将反映上一次运行的命令的退出状态$file(在前一种情况下,如果您获得1,则不知道是否是因为$file不存在或该命令是否失败)。

  • command . "$file"

    期望文件在那里,但是如果无法解释,不要退出。

  • [ ! -e "$file" ] || command . "$file"

    结合以上两种方法:如果文件不存在就可以了,对于POSIX Shell,报告了打开(或解析)文件失败但不是致命的(可能更适合~/.profile)。


¹注意:zsh但是,command除非进行sh仿真,否则您不能那样使用;请注意,在Korn shell中,source实际上是的别名command .,是的非特殊变体.


有趣!我不了解POSIX sh。但是问题是关于.bash_profile。我想比后悔更安全,但是bash是从POSIX模式获得.bash_profile的吗?
Mikel

(我意识到您可以通过阅读问题中的nvm源链接将这个问题解释为更广泛地应用于所有POSIX shell。)
Mikel

@Mikel,我的答案仍然适用于bash不在POSIX模式下的情况。[ -e /file ] && . /file如果文件不存在,您不希望它是一个错误,那么您会想要的。如果无法在此处执行尝试,则try源将处理该错误
斯特凡Chazelas

1
@Mikel,适得其反。1)不能阻止POSIX shell在错误时退出(或POSIX模式下的bash)2)复制错误消息(您在stdout上),.将已经报告错误(在stderr上)。而且如果目的是在文件不存在时不将其视为错误,那是不正确的(并且不可能从退出状态中判断是否.由于文件不存在或不可读或是否失败而失败)。无法解析,或者最后一个命令失败),这就是我在此答案中要指出的要点。
斯特凡Chazelas

1
关于竞争状况-IMHO 一次登录失败(系统上正在发生奇怪的事情)的问题要比登录持续失败(即使用户配置中有一些不正确的问题)少-文件)。因此,与没有检查相比,此检查中的比赛条件仍然是一种改进。
ruakh

5

nvm的回应维护者:

只需删除文件即可轻松卸载nvm;强制进行额外的工作(以跟踪源nvm所在的行)似乎并不特别有价值。

我的解释(结合斯特凡的出色解释和库萨兰南达的评论):

更简单,更安全。

它可以防止由于缺少文件(由于各种原因)而在启动时退出POSIX Shell。那些使用非POSIX(例如bash)shell的用户可以根据需要删除条件。


1
我反对您的解释是“针对初学者”。是防御性的。您不希望用户的登录外壳程序在启动时意外终止,因为该文件丢失了(无论出于何种原因)。如果该行代码位于中的shell初始化文件之一中/etc,则它允许某些用户拥有该文件,而某些用户则没有该文件。恕我直言,nvm维护者的反应只是一方面。
库沙兰丹

1

正如JBallinStéphaneChazelas所指出的那样,在POSIX shell中,获取不存在的文件将导致登录失败。

但是,添加测试以查看文件是否存在,然后尝试为其提供源,可能会导致出现竞争状况。如果nvm.sh介于[ -s nvm.sh ]和之间的内容发生变化. nvm.sh,则将导致他们正试图阻止的错误,尽管这种情况很少发生。

通常,防止争用情况的方法是尝试尝试您想做的事情,然后在错误失败时处理错误,例如

. "$NVM_DIR/nvm.sh" || echo "Sourcing $NVM_DIR/nvm.sh failed" >&2

事实证明,这在POSIX Shell中不起作用,因为如上所述,.失败将导致Shell立即退出,然后再运行任何错误处理。

我的回答认为POSIX shell与该问题无关,因为.bash_profile永远不要在POSIX模式下运行。因此,我们无论如何都可以执行上面的代码。

为了最安全,我们可以确保POSIX模式无效,或者使用/unix//a/383581/3169中描述的技术来确保禁用POSIX模式。

Stéphane的答案为如何处理所有POSIX shell提供了一些有用的建议,我认为这是nvm作者的意图,但与这里的问题有细微的区别,这就是为什么我们有多种可能的方法,这取决于您的目标是什么。

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.