区分常规文件和符号链接

区分常规文件和符号链接

我正在编写一个 bash 脚本,需要区分常规文件和符号链接。我以为我可以使用 if/test 表达式来做到这一点,但它并没有像我预期的那样工作:

$ touch regular_file
$ test -f regular_file; echo $?
0
$ test -h regular_file; echo $?
1
$ ln -s regular_file symlink
$ test -h symlink; echo $?
0
$ test -f symlink; echo $?
0

这是为什么?而且,我怎样才能正确地做到这一点?

答案1

看起来你只是稍微打乱了你的测试。您不需要运行这两个测试,在这种情况下,您唯一需要的测试是-h告诉您该文件是否是符号链接的测试。

test -h file && echo "is symlink" || echo "is regular file"

您使用的测试-f仅告诉您该对象是文件还是文件的链接。1如果它是目录、设备节点、目录的符号链接等,则这将返回(失败),但0如果它是文件的符号链接,则将返回(成功)。换句话说,一些符号链接将评估为 true,其他符号链接将评估为 false。

如果您还需要知道它是否是文件而不是目录的符号链接,则需要将两个测试的结果与一点逻辑结合起来。

答案2

@Caleb 关于使脚本仅测试符号链接是正确的。然而,关于为什么的部分被遗漏了,我很好奇。如果您查看 coreutils 源代码并跟踪测试的输出,您可以看到,当您运行符号链接测试时,它使用 lstat,如果您使用 -f 测试,它实际上会调用符号链接后面的“stat”:

$ ln -s varnish_config XXX
$ strace -s 2000 test -L XXX 2>&1 | grep XXX
execve("/usr/bin/test", ["test", "-L", "XXX"], [/* 47 vars */]) = 0
lstat("XXX", {st_mode=S_IFLNK|0777, st_size=14, ...}) = 0

$ strace -s 2000 test -L varnish_config 2>&1 | grep varnish
execve("/usr/bin/test", ["test", "-L", "varnish_config"], [/* 47 vars */]) = 0
lstat("varnish_config", {st_mode=S_IFREG|0664, st_size=1046, ...}) = 0

$ strace -s 2000 test -f XXX 2>&1 | grep XXX
execve("/usr/bin/test", ["test", "-f", "XXX"], [/* 47 vars */]) = 0
stat("XXX", {st_mode=S_IFREG|0664, st_size=1046, ...}) = 0

从统计手册页:

   stat() stats the file pointed to by path and fills in buf.

   lstat() is identical to stat(), except that if path is a symbolic link,
   then the link itself is stat-ed, not the file that it refers to.

这意味着只要指定的文件名是常规文件或常规文件本身的符号链接,-f 测试就会返回 true。

答案3

大多数test测试与文件相关的操作员都会这样做如果是的话,符号链接解析test -f symlink将返回 truesymlink常规的符号链接解析后确定的文件,对于test -s symlink非空文件或test -d symlink目录文件或test -r symlink可读文件相同。

它使用stat()系统调用来做到这一点。

唯一的例外是 with -L(通常优于-h),为了测试文件是否是符号链接,需要执行 alstat()来获取有关链接本身的信息,而不是它最终解析为的文件。

所以要检查一个文件是否是常规的文件符号链接解析,您需要:

if test -f "$file" && test ! -L "$file"; then
  echo is a regular file
fi

为了消除符号链接、常规类型和其他类型之间的歧义,您需要执行第test -L一个操作:

if test -L "$file"; then
  echo symlink
elif test -f "$file"; then
  echo regular # strictly speaking, it could also have been replaced
               # with a symlink to a regular file since the previous test
else
  echo other type or not accessible
fi

相关内容