我正在编写一个 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