在
[ -f "$file" ]
该[命令在存储的路径上执行stat()(不是lstat())系统调用,如果该系统调用成功并且返回的文件类型为“ regular ” ,则$file返回true。stat()
因此,如果[ -f "$file" ]返回true,则可以判断该文件确实存在,并且是最终解析为常规文件的常规文件或符号链接(或至少在时是stat())。
但是如果返回错误(或者[ ! -f "$file" ]或! [ -f "$file" ]返回true),也有很多不同的可能性:
- 该文件不存在
- 该文件存在但不是常规文件(可以是设备,FIFO,目录,套接字...)
- 该文件存在,但是您没有对父目录的搜索权限
- 该文件存在,但是访问该文件的路径太长
- 该文件是与常规文件的符号链接,但是您没有对符号链接解析中涉及的某些目录的搜索权限。
- ...
stat()系统调用可能失败的任何其他原因。
简而言之,应该是:
if [ -f "$file" ]; then
printf '"%s" is a path to a regular file or symlink to regular file\n' "$file"
elif [ -e "$file" ]; then
printf '"%s" exists but is not a regular file\n' "$file"
elif [ -L "$file" ]; then
printf '"%s" exists, is a symlink but I cannot tell if it eventually resolves to an actual file, regular or not\n' "$file"
else
printf 'I cannot tell if "%s" exists, let alone whether it is a regular file or not\n' "$file"
fi
为了确定文件不存在,我们需要stat()系统调用返回错误代码ENOENT(ENOTDIR告诉我们其中一个路径组件不是目录,这是另一种情况,我们可以告诉文件不存在通过该路径存在)。不幸的是,该[命令没有让我们知道这一点。无论错误代码是ENOENT,EACCESS(拒绝权限),ENAMETOOLONG还是其他任何值,它都将返回false。
该[ -e "$file" ]测试也可以做ls -Ld -- "$file" > /dev/null。在这种情况下,尽管无法轻松地以编程方式使用这些信息,但ls会告诉您stat()失败的原因:
$ file=/var/spool/cron/crontabs/root
$ if [ ! -e "$file" ]; then echo does not exist; fi
does not exist
$ if ! ls -Ld -- "$file" > /dev/null; then echo stat failed; fi
ls: cannot access '/var/spool/cron/crontabs/root': Permission denied
stat failed
至少ls告诉我这不是因为文件不存在而导致失败。这是因为它无法判断文件是否存在。该[命令只是忽略了该问题。
使用zshshell,您可以$ERRNO在失败的[命令后使用特殊变量查询错误代码,并使用模块中的$errnos特殊数组对该数字进行解码zsh/system:
zmodload zsh/system
ERRNO=0
if [ ! -f "$file" ]; then
err=$ERRNO
case $errnos[err] in
("") echo exists, not a regular file;;
(ENOENT|ENOTDIR)
if [ -L "$file" ]; then
echo broken link
else
echo does not exist
fi;;
(*) syserror -p "can't tell: " "$err"
esac
fi
(请注意$errnos,zsh当使用的最新版本构建时,某些版本gcc的支持已被破坏)。