JavaFan has asked for the wisdom of the Perl Monks concerning the following question:

Given two filenames (paths), I want to know whether they point to the same file. I don't think File::Spec has a method for that, so I use the following:
# Return false if either files doesn't exist, or if we # cannot stat either file. sub is_same { my ($file1, $file2) = @_; return unless -f $file1 && -f $file2; return unless my ($dev1, $ino1) = (lstat $file1)[0, 1]; return unless my ($dev2, $ino2) = (lstat $file2)[0, 1]; return $dev1 == $dev2 && $ino1 == $ino2; }
Works like a charm on Unix.

I've no idea how that works under non-Unix systems.

(Note, I'm not trying to see if two paths are identical - File::Spec has methods for that. But files can have more than one name (hard links), or refer to another file (sym links) and I want to know if two, possibly different, paths point to the same file.)

Replies are listed 'Best First'.
Re: Testing if two paths point to the same file.
by ikegami (Patriarch) on Jan 30, 2009 at 13:47 UTC

    There's no reason to limit oneself to plain files. I'd remove the -f checks.

    The inode is always 0 on Windows+NTFS

    The device id is 0 for A:, 1 for B: .., 25 for Z:. It doesn't work with UNC paths such as "\\?\C:\". It doesn't check if two drives are the same with different names (e.g. when using SUBST).

Re: Testing if two paths point to the same file.
by codeacrobat (Chaplain) on Jan 30, 2009 at 14:59 UTC
    Cwd has got a method for it :-)
    perl -MTest::More=no_plan -MCwd=realpath -e ' is (realpath("/etc/passwd"), realpath("/etc/../etc/passwd")) ' ok 1 1..1

    print+qq(\L@{[ref\&@]}@{['@'x7^'!#2/"!4']});

      I don't think this is what the OP is asking for.

      Under *nix, if I do ln foo.txt bar.txt, then I have two directory entries pointing to the same physical file. The inode for this file is identical. Two different directory entries, one physical file.

      Your example checks to see if the ending of the files passes through the same part of the directory tree. One physical file, one directory entry (at least for the final part of the path).

      Now, if realpath would identify that foo.txt and bar.txt point to the same inode, then it would be identical. As it stands, it is not.

      Still a useful solution, however, to a different problem.

      --MidLifeXis

      Will that work for hard links?
        Technically, ".." is a hard link. But in general, no.
        $ touch foo $ ln foo bar $ perl -MTest::More=no_plan -MCwd=realpath -e ' is(realpath($ARGV[0]), realpath($ARGV[1])) ' foo bar not ok 1 # Failed test at -e line 2. # got: '/home/eric/foo' # expected: '/home/eric/bar' 1..1 # Looks like you failed 1 test of 1.
Re: Testing if two paths point to the same file.
by repellent (Priest) on Feb 01, 2009 at 23:29 UTC
    ++JavaFan

    Would it be better to use stat instead of lstat so it doesn't get fixated on symlinks? It's the final destination that we care about.

    Nitpick: Slice [0, 1] is redundant.
      stat will/may go in an infinite loop (until killed or defended against), unlike lstat, on a symbolic link loop.
        Really? On FreeBSD 6.3, Perl v5.8.8:
        $ ln -s i_scratch_your_back you_scratch_my_back $ ln -s you_scratch_my_back i_scratch_your_back $ cd you_scratch_my_back cd: too many levels of symbolic links: you_scratch_my_back $ perl -MData::Dumper -e '@s = stat("i_scratch_your_back"); print Dump +er(@s)'; date Tue Feb 3 07:31:40 PST 2009 $

        Despite this pathological case, stat does not defeat the purpose of the OP which was to find out if two paths pointed to the same (non-symlink) file. lstat on the other hand would fail is_same if tested with a plain file and its corresponding symlink.