我正在编写一个需要区分常规文件和符号链接的 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
Run Code Online (Sandbox Code Playgroud)
这是为什么?而且,我怎样才能正确地做到这一点?
Cal*_*leb 20
看起来你只是在扰乱你的测试。您不需要同时运行这两个测试,对于这种情况,您唯一需要的是-h
告诉您文件是否为符号链接的测试。
test -h file && echo "is symlink" || echo "is regular file"
Run Code Online (Sandbox Code Playgroud)
该-f
测试仅告诉您对象是否为文件。这将返回0
如果它是一个目录或设备节点或一个符号链接到一个目录,但将返回1
上一个符号链接到一个文件中。
如果您还需要知道它是否是文件的符号链接而不是目录,则需要将两个测试的结果与一些逻辑结合起来。
@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
Run Code Online (Sandbox Code Playgroud)
从统计手册页:
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.
Run Code Online (Sandbox Code Playgroud)
这意味着 -f 测试将返回 true,只要指定的文件名是到常规文件或常规文件本身的符号链接。