How can I detect that a symlink is broken in Perl?
Asked Answered
T

7

8

I would like to remove a broken symlink in a directory using Perl.

In my mind I just had to list the file of a directory and test is this a symlink (-l) and if it returns false just unlink it.

But it appears that when using readir to list all files my broken symlinks are not recoganized as a file. Because my link is pointing to nothing I understand why.

All the file in $myDir are symlinks, either valid or broken. When I display @files I only get a list of valid symlink.

opendir DIR, $myDir;
my @files = grep(/$regexp/,readdir(DIR));
closedir DIR;
print "filenames : @files\n";
Theorize answered 5/7, 2011 at 9:46 Comment(1)
What do you mean "are not recoganized as a file"? by what? show your code.Gurney
F
9

There are two main relevant system calls, stat() and lstat(). The lstat() call will tell you that it is a symlink (but on other files, behaves the same as stat()). This allows you to determine that the name is a symlink. The stat() system call follows a symlink to its end, and tells you about the file (or directory) at the end of the link. If the stat() call fails on the symlink, then the symlink is broken or you're trying to access a directory or file where you have no permission.

The Perl file test operators include -l to detect whether a name is a symlink. You can use the Perl functions stat and lstat explicitly. Between these, you should be able to sort out whether a symlink is broken or not - but you should probably plan to write a function to do the job.

You probably don't need to use the readlink Perl function. Beware the underlying system readlink() call; it does not return a null-terminated string!

It is interesting that neither Perl nor its POSIX module supports the realpath() function. However, the PathTools module does support it. If realpath fails, on a symlink, the symlink is non-functional (aka broken).

Facial answered 5/7, 2011 at 10:1 Comment(0)
P
8

Combining lstat with stat:

say "dangling link at $fn" if (lstat $fn and not stat $fn);

update: it works for me...

salva@topo:~/t/dl$ perl -E 'opendir $dh, "."; say $_ for grep { !stat $_ and lstat $_ } readdir $dh'
foo
salva@topo:~/t/dl$ ls -l
total 0
-rw-rw-r-- 1 salva salva  0 2011-07-05 12:34 f
lrwxrwxrwx 1 salva salva 11 2011-07-05 12:00 fii -> /etc/shadow
lrwxrwxrwx 1 salva salva 12 2011-07-05 11:59 foo -> /etc/hjdkshf
Presidentelect answered 5/7, 2011 at 10:3 Comment(0)
S
6

Here's some code I've used to remove broken links:

chdir $dir        or die;
opendir(DIR, '.') or die;

foreach my $link (readdir DIR) {
  next unless -l $link and not -e readlink($link);

  print "Removing broken link $link\n";
  unlink $link;
}

closedir DIR;

Note that it's important that the directory containing the links is the current directory. readdir returns only filenames, and the links might be relative.

State answered 5/7, 2011 at 10:0 Comment(1)
+1: Good point about relativity of names. You could add the value from readdir to the name (relative or absolute) of the directory and work from that: next unless -l "$dir/$link" and not -e "$dir/$link". I omitted the readlink in that rewrite; I think that is OK since the O/S reads the link for you.Facial
G
3

Check for broken symlinks (checking only the top level if there are symlinks to symlinks):

use strict;
use warnings;
use autodie;
opendir my $dirh, '.';
while (my $file = readdir $dirh) {
    if ( -l $file ) {
        my $target = readlink $file;
        if ( ! -e $target && ! -l $target ) {
            print "$file -> $target broken\n";
        }
    }
}
Gurney answered 5/7, 2011 at 10:0 Comment(0)
S
2

Use readlink() and stat() the result.

Subsellium answered 5/7, 2011 at 10:0 Comment(0)
L
0

Using the built-in Perl glob function ? For examples:

 @files = <*>;
 foreach $file (@files) {
   print $file . "\n";
 }

For a specific $dir:

 @files = <$dir*>;
 foreach $file (@files) {
   print $file . "\n";
 }
Labored answered 27/12, 2011 at 13:20 Comment(1)
That doesn't work if $dir contains characters that are special in glob, such as spaces or * or ?.Ibanez
F
0

A broken symlink is a link (-l) that does not exists (!-e)

perl -e 'print "broken: $_\n" for grep { -l and ! -e } glob("*");'
Fridlund answered 30/1, 2023 at 14:13 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.