http://qs1969.pair.com?node_id=537601

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

Hi,

I've got a script that will read some folder paths into an array and then index the filenames in each folder in a table. However, I would like to first check if the folder is actually a symbolic link to a physical directory, in order to avoid duplicate entries for the same files.

Is there a way to check if a folder is a symbolic link?

Thanks,
Ralph

  • Comment on Detecting if a folder is a symbolic link

Replies are listed 'Best First'.
Re: Detecting if a folder is a symbolic link
by friedo (Prior) on Mar 17, 2006 at 22:49 UTC
    if( -l $file ) { # file is a symlink }
Re: Detecting if a folder is a symbolic link
by merlyn (Sage) on Mar 17, 2006 at 23:16 UTC
    To avoid duplicates, you can sidestep the symlink issue (because any given file might also be a symlink) by stat'ing everything you process and rejecting anything where the dev/inode pair is the same. In other words:
    BEGIN { my %seen; sub process { my $entry = shift; my ($dev, $ino) = stat $entry or return; $seen{"$dev $ino"}++ or return; ... rest of processing ... } }

    -- Randal L. Schwartz, Perl hacker
    Be sure to read my standard disclaimer if this is a reply.

Re: Detecting if a folder is a symbolic link
by Tanktalus (Canon) on Mar 17, 2006 at 23:04 UTC

    friedo points out -l. I'll add two more pieces: using lstat (which is what -l uses i under the covers), which gets way too complicated, and using readlink to read the symlink.

    Note that while you can use the "_" special variable for the -X operators, you need to be careful when you're involving the -l. You can't do if (-d $file && -l _). That will always return false. You must reverse that: if (-l $file && -d _). The reason is that the -d check (and everything except -l) will use stat which follows symlinks reading the underlying file, directory, device, FIFO, whatever. That will never be a symlink (unless it points to a nonexistant entity). Of course, if it points to a nonexistant entity, it won't be a directory ;->

    if (-d $file && -l $file) should work fine, albeit the tiniest bit more expensive than if (-l $file && -d _).

    Also be careful with readlink. It can be awfully tricky to follow symlinks the way that the OS does. Symlinks to symlinks to files inside symlink'd directories ... it's really convoluted. You're probably better off using stat (not lstat) and using the first two fields (dev and ino) as hash keys to keep track of whether you've seen it or not. This will actually also catch hardlinked files which you probably aren't even thinking about ;-)

    { my %seen; sub have_seen_file { my $file = shift; my ($dev, $ino) = stat($file); $seen{$dev}{$ino}++; } sub reset_seen_files { %seen = () } }
    Or something like that.

    Update: tye is right - I normally actually use this shortcut as "if (-l $file || -d _)" which, of course, is not what the OP wanted.

      You can't do if (-d $file && -l _). That will always return false.

      True.

      You must reverse that: if (-l $file && -d _).

      Actually, that also always returns a false value. The cached lstat results did not follow the symbolic link and so doesn't know anything about what (if anything) it links to. If you want to check for "X is a (symbolic) link to a directory", then you can't avoid doing [l]stat twice (well, lstat once and stat once).

      There certainly are cases where you can "cheat" (or "be efficient"). Perhaps you were thinking of a very common case, for example:

      if( ! -l $file && -d _ ) { } #or if( -l $file ) { ... } elsif( -d _ ) { ... } elsif( -f _ ) { ... } else { ... }

      That does work and does require that you do the -l part first.

      - tye        

      What does "_" mean here? ( Referring to -d _ )

        It refers to the previous call to stat or other file test. Using the underscore saves a useless second call.

        If stat is passed the special filehandle consisting of an underline, no stat is done, but the current contents of the stat structure from the last stat, lstat, or filetest are returned.

        🦛

Re: Detecting if a folder is a symbolic link
by borodache (Novice) on Mar 28, 2018 at 13:02 UTC
    Hi All,
    I am doing a bit of a different script, but I need the same functionality. I need to distinguish between files, folders and links. I am using: if (-l $file), if (-d $file) and if (-f $file).
    However, it doesn't work. Files are indeed classified as files (I don't know about link to files cause I don't have them), but links (to directories) are classified as directories and not links. I dag the whole web and found that -l works only on links appropriates to the OS. However, even when I created on Windows link using mklink, it was classified as a directory. Does somebody has any idea?

      If you look at the documentation for the -X functions, the -l test is described as:

      File is a symbolic link (false if symlinks aren't supported by the file system).

      If you look at the Alphabetical Listing of Perl Functions section of perlport, the following note is listed under -X:

      (Win32, VMS, RISC OS) -g , -k , -l , -u , -A are not particularly meaningful.

      Based on those documents, it looks like -l is not going to work on Windows.

      I'm not saying this is the best alternative, but one alternative that you could consider is the testL function from the Win32::LongPath module.

        Hi,
        I am doing this script in my workplace, and we are not allowed to add any external (cpan) library. Is there another solution?
Re: Detecting if a folder is a symbolic link
by ralphch (Sexton) on Mar 19, 2006 at 14:33 UTC

    Hi, thanks for all of your replies! I used if(!-l $file && -d $file) and it's working great.

    Regards,
    Ralph

Re: Detecting if a folder is a symbolic link
by borodache (Novice) on Mar 28, 2018 at 13:53 UTC
    https://perldoc.perl.org/functions/-X.html
    "-l File is a symbolic link (false if symlinks aren't supported by the file system)" - Does it mean -l doesn't work on Windows?