How to find the target of a Unix symlink?

K

kj

How does one find the target(s) of a Unix symlink?

I guess one klugey way would be to pick through the output of
"/bin/ls -al":

sub get_target {
my ($link, $seen) = @_;
$seen ||= {};
return $link if $seen->{$link}; # circularity
return $link unless -e $link;
my $ls = (`/bin/ls -al $link`)[0]; # ick!
return $link unless $ls =~ /\s${link} ->\s+(.*?)\s*$/;
$seen->{$link} = 1;
get_target($1, $seen);
}

Is there a more civilized way to do this?

Thanks!

kj
 
T

Todd

kj said:
How does one find the target(s) of a Unix symlink?

I guess one klugey way would be to pick through the output of
"/bin/ls -al":

sub get_target {
my ($link, $seen) = @_;
$seen ||= {};
return $link if $seen->{$link}; # circularity
return $link unless -e $link;
my $ls = (`/bin/ls -al $link`)[0]; # ick!
return $link unless $ls =~ /\s${link} ->\s+(.*?)\s*$/;
$seen->{$link} = 1;
get_target($1, $seen);
}

Is there a more civilized way to do this?

Thanks!

kj


perldoc -f readlink

my $target = readlink $link_name;

Todd
 
K

kj

In said:
How does one find the target(s) of a Unix symlink?
I guess one klugey way would be to pick through the output of
"/bin/ls -al":
sub get_target {
my ($link, $seen) = @_;
$seen ||= {};
return $link if $seen->{$link}; # circularity
return $link unless -e $link;
my $ls = (`/bin/ls -al $link`)[0]; # ick!
return $link unless $ls =~ /\s${link} ->\s+(.*?)\s*$/;
$seen->{$link} = 1;
get_target($1, $seen);
}

Well, the above is clearly a disaster...
Is there a more civilized way to do this?

I found readlink, but my gripe with it is that it doesn't follow
links beyond the first hop. (I.e. if "first" points to "second",
and "second" points to "third", readlink "first" returns "second"
not "third.) stat, on the other hand, does a nice job of finding
the ultimate target of a sequence of links, but one ends up with
a device and an inode, not a filename for the target.

Is there a way to get the filename (or filenames) associated with
a dev+inode combination?

Thanks in advance,

kj
 
D

Darren Dunham

kj said:
I found readlink, but my gripe with it is that it doesn't follow
links beyond the first hop. (I.e. if "first" points to "second",
and "second" points to "third", readlink "first" returns "second"
not "third.) stat, on the other hand, does a nice job of finding
the ultimate target of a sequence of links, but one ends up with
a device and an inode, not a filename for the target.
Is there a way to get the filename (or filenames) associated with
a dev+inode combination?

No, not easily. The System rarely (never?) needs to do this, so there
is no index for it.

You would have to 'find' on the filesystem (ususally expensive), trying
to match the inode, and must realize that there may be more than one
filename for it.
 
B

Ben Morrow

Quoth kj said:
I found readlink, but my gripe with it is that it doesn't follow
links beyond the first hop. (I.e. if "first" points to "second",
and "second" points to "third", readlink "first" returns "second"
not "third.)

This is the only way to get all the information. A simple way to follow
all links:

sub readalllinks {
my $file = shift;
while (-l $file) {
$file = readlink $file;
}
}
stat, on the other hand, does a nice job of finding
the ultimate target of a sequence of links, but one ends up with
a device and an inode, not a filename for the target.

Is there a way to get the filename (or filenames) associated with
a dev+inode combination?

No, except in special cases.

Ben
 
K

kj

This is the only way to get all the information. A simple way to follow
all links:
sub readalllinks {
my $file = shift;
while (-l $file) {
$file = readlink $file;
}
}

Hmm. I think this fails if $file's target is outside $file's
directory and is specified using a relative path. I think this
does "the right thing":

use Cwd;
use File::Basename;
sub readalllinks {
my $cwd = my $dir = cwd . '/';
my $file = shift;
my $path = ($file =~ m,^/,) ? $file : "${dir}${file}";
$dir = (fileparse($path))[1];

while (-l $path) {
$file = readlink $path;
$path = ($file =~ m,^/,) ? $file : "${dir}${file}";
$dir = (fileparse($path))[1];
}
(my $basename, $dir) = fileparse $path;
chdir $dir;
$path = cwd . "/$basename";
chdir $cwd;
$path;
}

....but it is eggly.

kj
 
C

chris-usenet

Darren Dunham said:
You would have to 'find' on the filesystem (ususally expensive), trying
to match the inode, and must realize that there may be more than one
filename for it.

Or zero matches (the file might have been unlinked but still in use).

Chris
 
B

Ben Morrow

Quoth kj said:
Hmm. I think this fails if $file's target is outside $file's
directory and is specified using a relative path.

Yes, of course it does. Whoops :)
I think this
does "the right thing":

...but it is eggly.

Having seen your use of Cwd, I remembered that this will do just fine:

use Cwd qw/realpath/;

my $file = realpath($file);

:)

But a cleaner rewrite would be

use Cwd qw/cwd/;
use File::Spec::Functions qw/rel2abs catpath splitpath/;

sub readalllinks {
my $rel = shift;
my $dir = cwd;
my $abs;

while ( -l ($abs = rel2abs $rel, $dir) ){
$dir = catpath +(splitpath $abs)[0,1];
$rel = readlink $abs;
}

return $abs;
}
 
J

Joe Smith

kj said:
Is there a way to get the filename (or filenames) associated with
a dev+inode combination?

To get an idea as to how difficult that is, read this:
ftp://lsof.itap.purdue.edu/pub/tools/unix/lsof/FAQ

-Joe
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,007
Latest member
obedient dusk

Latest Threads

Top