如何在Bash中检查两个路径是否相等?

33

在Bash中,检查两个路径是否相等的最佳方法是什么?例如,给定以下目录结构:

~/
  Desktop/
    Downloads/ (symlink to ~/Downloads)
  Downloads/
    photo.png

假设当前目录是主目录,则以下所有内容等效:

./                    and ~
~/Desktop             and /home/you/Desktop
./Downloads           and ~/Desktop/Downloads
./Downloads/photo.png and ~/Downloads/photo.png

有没有一种本地的Bash方法可以做到这一点?


大多数答案(除了我的)都会在绑定挂载和其他可以使两个路径引用相同内容的符号链接等情况下中断。 - Peter Cordes
另请参阅:https://dev59.com/_HVC5IYBdhLWcg3wdw65 相关链接:https://unix.stackexchange.com/questions/206973/how-to-find-out-whether-two-directories-point-to-the-same-location - Ciro Santilli OurBigBook.com
1
关于@Ciro的链接:如何在Bash中规范化文件路径?将帮助您检查所需的路径相等性,但如果您想要实际检查2个路径是否是磁盘上的同一文件/目录,则无法处理绑定挂载。 (这就是此处接受的答案所做的。)如何找出两个目录是否指向相同位置有回答可以做到这一点,但当前接受的答案只是路径规范化。 - Peter Cordes
5个回答

49
Bash的测试命令中有一个用于此目的的`-ef`运算符:
if [[ ./ -ef ~ ]]; then ...

if [[ ~/Desktop -ef /home/you/Desktop ]]; then ...

这是这个操作符的文档:
$ help test | grep -e -ef
      FILE1 -ef FILE2  True if file1 is a hard link to file2.
$ help '[['
[[ ... ]]: [[ expression ]]
    Execute conditional command.

    Returns a status of 0 or 1 depending on the evaluation of the conditional
    expression EXPRESSION.  Expressions are composed of the same primaries used
    by the `test' builtin, and may be combined using the following operators:

      ( EXPRESSION )    Returns the value of EXPRESSION
      ! EXPRESSION              True if EXPRESSION is false; else false
      EXPR1 && EXPR2    True if both EXPR1 and EXPR2 are true; else false
      EXPR1 || EXPR2    True if either EXPR1 or EXPR2 is true; else false

[...snip...]

请注意,为了使此操作生效,运算符的两个路径都必须引用现有的文件或目录。如果文件或目录不存在,则测试将返回false。
如果您愿意,您可以使用`test`或`[`内置命令而不是双括号:
if test ./ -ef ~; then ...

if [ ./ -ef ~ ]; then ...

但是为了一致性,我们更倾向于使用[[ ... ]],因为它包含了test[的全部功能,还有其他特性,比如模式匹配和正则表达式匹配。

3
好的!看起来这个方法能够正确地比较设备号和i节点号,所以我接受了这个答案。 - James Ko
if [ ./ -ef ~ ]; then should be enough, test is synonymous to [ - tesch1
确认,它可以跨绑定挂载工作,使我的 find -samefile 答案过时了。 - Peter Cordes
它帮助我解决了使用Windows Git Bash时,bashrc路径逻辑运算符比较问题,使得cd命令更加方便。谢谢。 - ajay4q
我不反对,但目前的回答似乎并没有表明这一点。 - undefined
显示剩余4条评论

6
您可以使用realpath函数。例如:
realpath ~/file.txt
Result: /home/testing/file.txt

realpath ./file.txt
Result: /home/testing/file.txt

同时查看类似问题的答案:bash/fish命令打印文件的绝对路径


3

本地Bash方式:

pwd -P 返回物理目录,而不考虑符号链接。

cd "$dir1"
real1=$(pwd -P)
cd "$dir2"
real2=$(pwd -P)
# compare real1 with real2

另一种方法是使用cd -P,它会跟随符号链接,但将您留在物理目录中:
cd -P "$dir1"
real1=$(pwd)
cd -P "$dir2"
real2=$(pwd)
# compare real1 with real2

这仅适用于符号链接。如果相同的文件系统在多个点上挂载(例如mount --bind /raid0/var-cache /var/cache),那么您最好比较inode。 - Peter Cordes
1
这并不完全符合您的要求,因为第一个目录更改会影响第二个目录中任何相对路径的解释。为了解决这个问题,您应该将 cd 移到命令替换内部(以便它在子 shell 中运行)。 - ruakh
@ruakh提出了一个非常好的观点,我之前忽略了。geirha的回答也很有趣。 - Tom Zych

3
如果您已经安装了coreutils 8.15或更高版本,则可以使用realpath命令完全规范化路径。它会删除任何...组件,使路径绝对,并解析所有符号链接。因此:
if [ "$(realpath "$path1")" = "$(realpath "$path2")" ]; then
   echo "Same!"
fi

3

基于解决符号链接的方法在存在其他因素时可能会失败。例如,绑定挂载(像mount --bind /raid0/var-cache /var/cache)。

使用find -samefile是一个不错的选择。这将比较文件系统和inode编号。

-samefile是GNU的扩展。即使是在Linux上,busybox find可能也没有这个功能。GNU用户空间和Linux内核通常一起使用,但您可以单独使用其中任何一个,而此问题仅标记为Linux和bash。

# params: two paths.  returns true if they both refer to the same file
samepath() {
    # test if find prints anything
    [[ -s "$(find -L "$1" -samefile "$2")" ]]  # as the last command inside the {}, its exit status is the function return value
}

例如,在我的系统上:
$ find /var/tmp/EXP/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb  -samefile /var/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb 
/var/tmp/EXP/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb


$ stat {/var/tmp/EXP,/var}/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb
  File: ‘/var/tmp/EXP/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb’
...
Device: 97ch/2428d      Inode: 2147747863  Links: 1
...

  File: ‘/var/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb’
Device: 97ch/2428d      Inode: 2147747863  Links: 1

你可以使用find -L,以便在最终路径组件中跟随符号链接:
$ ln -s   /var/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb foo
$ find    /var/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb  -samefile foo
$ find -L /var/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb  -samefile foo
/var/cache/apt/archives/bash_4.3-14ubuntu1_amd64.deb

显然,这适用于引用目录或任何类型的文件的路径,而不仅仅是常规文件。它们都有inode号。


用法:

$ samepath /var/cache/apt/  /var/tmp/EXP/cache/apt/  && echo true
true
$ ln -sf /var/cache/apt foobar
$ samepath foobar /var/tmp/EXP/cache/apt/  && echo true
true
samepath /var/tmp/EXP/cache/apt/ foobar   && echo true
true
samepath foobar   && echo true   # doesn't return true when find has an error, since the find output is empty.
find: `': No such file or directory

find -L会为-samefile和路径列表解除符号链接。因此,这两者都可以是符号链接。


1
+1. 你能否添加一些关于可移植性的信息呢?(似乎 POSIX 没有指定“-samefile”?) - ruakh
@ruakh:哦,对了,这是GNU的扩展,可能只在GNU“find”中找到。显然,“bash”不带有GNU find,但我认为“linux”标签让我忽略了这一点提醒。 - Peter Cordes

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接