33

What's the best way to check if two paths are equal in Bash? For example, given the directory structure

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

and assuming that the current directory is the home directory, all of the following would be equivalent:

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

Is there a native Bash way to do this?

3

5 Answers 5

49

Bash's test commands have a -ef operator for this purpose:

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

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

Here is the documentation for this operator:

$ 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...]

Note that both paths of the operator have to refer to an existing file or directory for this to work. If the file or directory does not exist, the test will return false.

If you prefer, you can use the test or [ builtins instead of double brackets:

if test ./ -ef ~; then ...

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

but [[ ... ]] is preferred for consistency, since it encompasses the complete functionality of test and [ in addition to other features, such as pattern matching and regex matching.

9
  • 3
    Nice! It looks like this method properly compares device and inode numbers as well, so I'm accepting this answer.
    – James Ko
    Commented Nov 29, 2015 at 18:15
  • if [ ./ -ef ~ ]; then should be enough, test is synonymous to [
    – tesch1
    Commented Aug 23, 2018 at 1:04
  • Confirmed, it works across bind mounts, making my find -samefile answer obsolete. Commented Jan 9, 2021 at 1:25
  • It helped me with windows git bashrc path logical operator comparision for cd-ing. Thanks.
    – ajay4q
    Commented Feb 26, 2022 at 21:15
  • @geirha Can you please explain why you rolled back the previous edit? It added useful information and formatting fixes, as well as an important consistency fix - [[ is emphatically not the same as test.
    – tripleee
    Commented Oct 22, 2023 at 14:24
6

You can use realpath. For example:

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

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

Also take a look at a similar answer here: bash/fish command to print absolute path to a file

3

If you have coreutils 8.15 or later installed, you have a realpath command that fully normalizes a path. It removes any . and .. components, makes the path absolute, and resolves all symlinks. Thus:

if [ "$(realpath "$path1")" = "$(realpath "$path2")" ]; then
   echo "Same!"
fi
3

Native bash way:

pwd -P returns the physical directory irrespective of symlinks.

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

Another way is to use cd -P, which will follow symlinks but leave you in the physical directory:

cd -P "$dir1"
real1=$(pwd)
cd -P "$dir2"
real2=$(pwd)
# compare real1 with real2
3
  • This only works for symlinks. If the same filesystem is mounted at multiple points (e.g. mount --bind /raid0/var-cache /var/cache), then your best bet is to compare inodes. Commented Nov 29, 2015 at 7:37
  • 1
    This doesn't do quite what you want, since the first directory-change affects the interpretation of any relative path in the second one. To fix this, you should move the cd inside the command substitution (so it runs inside the subshell).
    – ruakh
    Commented Nov 29, 2015 at 8:15
  • @ruakh makes an excellent point that I'd overlooked. geirha's answer is also quite interesting.
    – Tom Zych
    Commented Nov 29, 2015 at 12:30
3

Methods based on resolving symlinks fail when there are other factors involved. For example, bind mounts. (Like mount --bind /raid0/var-cache /var/cache

Using find -samefile is a good bet. That will compare filesystem and inode number.

-samefile is a GNU extension. Even on Linux, busybox find probably won't have it. GNU userspace and Linux kernel often go together, but you can have either without the other, and this question is only tagged Linux and 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
}

e.g. on my system:

$ 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

You can use find -L for cases where you want to follow symlinks in the final path component:

$ 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

Obviously this works for paths which refer to directories or any type of file, not just regular files. They all have inode numbers.


usage:

$ 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

So find -L dereferences symlinks for -samefile, as well as for the list of paths. So either or both can be symlinks.

2
  • 1
    +1. Could you add some information about portability, though? (It doesn't look like -samefile is specified by POSIX?)
    – ruakh
    Commented Nov 29, 2015 at 8:12
  • @ruakh: oh right, it's a GNU extension, probably only found in GNU find. Obviously bash doesn't come with GNU find, but I think the linux tag made me leave out a mention of this. Commented Nov 29, 2015 at 8:17

Not the answer you're looking for? Browse other questions tagged or ask your own question.