在
[ -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
告诉我这不是因为文件不存在而导致失败。这是因为它无法判断文件是否存在。该[
命令只是忽略了该问题。
使用zsh
shell,您可以$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
的支持已被破坏)。