2

I am looking for simple & proper way to obtain absolute path of symlink's immediate target (not final target), in perl.

My Example Tree:

|-- dir1
|   |-- link1 -> ../dir2/link2
|-- dir2
|   `-- link2 -> ../dir3/file.txt
|-- dir3
|   `-- file.txt
`-- test
    `-- script.pl

Code1:

my $link = dirname($ARGV[0]).'/'.readlink($ARGV[0]); 
print rel2abs($link), "\n";

Cmd: ./script.pl ../dir1/link1

Result: /tmp/perl_script_test/test/../dir1/../dir2/link2

Expected: /tmp/perl_script_test/dir2/link2

I am looking for absolute path of symlink's immediate target without ../

I tried abs_path(), realpath() but they all give path to final target (file.txt)

Is there any build-in function that does what I want to do I need to split the path and do manual concatenation ?

--

Mohan G

Mohan
  • 141
  • 3
  • 13

1 Answers1

1

File::Path's absolute doesn't do any file system checks.

use File::Path qw( file );

my $link_qfn = file(...);

my $link_dir_qfn = $link_qfn->dir;
my $target_qfn   = $link_dir_qfn->file(readlink($link_qfn));
my $target_fqfn  = $target_qfn->absolute;

say $target_fqfn;

Say the CWD is /tmp/perl_script_test/test

$link_qfn     ../dir1/link1
$link_dir_qfn ../dir1
$target_qfn   ../dir1/../dir2/link2
$target_fqfn  /tmp/perl_script_test/test/../dir1/../dir2/link2

Say the CWD is /tmp/perl_script_test

$link_qfn     dir1/link1
$link_dir_qfn dir1
$target_qfn   dir1/../dir2/link2
$target_fqfn  /tmp/perl_script_test/dir1/../dir2/link2

You've added to the following to your question:

I am looking for absolute path of symlink's immediate target without ../

You can't safely do that without possibly resolving symlinks. For example,

/tmp/perl_script_test/dir1/../dir2/link2

is not necessarily equivalent to

/tmp/perl_script_test/dir2/link2

because

/tmp/perl_script_test/dir1 

could be a symlink.


The following might be sufficient for you:

use Cwd        qw( real_path );
use File::Path qw( dir file );

my $link_qfn = file(...);

my $link_dir_qfn    = $link_qfn->dir;
my $target_qfn      = $link_dir_qfn->file(readlink($link_qfn));
my $target_fn       = $target_qfn->basename;
my $target_dir_qfn  = $target_qfn->dir;
my $target_dir_fqfn = dir(real_path($target_dir_qfn));
my $target_fqfn     = $target_dir_fqfn->file($target_fn);

say $target_fqfn;

Say the CWD is /tmp/perl_script_test/test

$link_qfn        ../dir1/link1
$link_dir_qfn    ../dir1
$target_qfn      ../dir1/../dir2/link2
$target_fn       link2
$target_dir_qfn  ../dir1/../dir2
$target_dir_fqfn /tmp/perl_script_test/dir2
$target_fqfn     /tmp/perl_script_test/dir2/link2

You will only get the above output if none of the following are symlinks:

/tmp
/tmp/perl_script_test
/tmp/perl_script_test/test
/tmp/perl_script_test/dir1
/tmp/perl_script_test/dir2

(You could make it work even if the first two are symlinks, but that would take even more work.)

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • Still I get: `/tmp/perl_script_test/test/../dir2/link2` – Mohan Apr 30 '19 at 17:31
  • That is what you asked for. Are you complaining that it doesn't produce `/tmp/perl_script_test/dir2/link2`? There's no way to know that without performing file system tests because `/tmp/perl_script_test/test` could be a symlink, and you specifically asked not to resolve any other symlinks. – ikegami Apr 30 '19 at 18:58
  • You're two criteria (resolving `..` and not resolving symlinks) are incompatible. – ikegami Apr 30 '19 at 20:11
  • My code was already giving `/tmp/perl_script_test/test/../dir1/../dir2/link2`. I am looking for absolute path without `../`. Sorry deleted by mistake. – Mohan Apr 30 '19 at 20:13
  • Also your code does not work if I execute the script from `/tmp/perl_script_test/` or `/tmp`. Yes I am looking for absolute path of symlink's immediate target without `../`. – Mohan Apr 30 '19 at 20:14
  • Fixed the problem identified in the last comment. Could continue to use File::Spec::Functions, but Path::Tiny is a wrapper for File::Spec::Functions that's easier to use. – ikegami Apr 30 '19 at 20:28
  • Added to answer. – ikegami Apr 30 '19 at 20:50