Well I've hacked this, but I'm not very proud of it and I hope someone comes up with something better. I've searched the CPAN but am surprised to have found nothing relevant
My idea was to use the abs2rel
function from File::Spec::Functions
. That's fine, except that it tries too hard for this purpose and will return ../..
for abs2rel('/usr', '/usr/a/b')
. It will also return the first value unchanged on systems that use volumes in the path
This just wraps abs2rel
inside a function is_within
which rejects both of those cases, but otherwise returns the relative path (a true value) intact. That means that is within('/usr', '/usr')
will return .
, which is true, but you can test it for that specific case if you think a directory shouldn't contain itself
Note that this doesn't check whether the paths are to directories, nor does it check whether the paths even exist
use strict;
use warnings 'all';
use File::Spec::Functions qw/ abs2rel file_name_is_absolute updir /;
my @pairs = (
[qw{ /usr /usr/bin } ],
[qw{ /usr/etc /usr/bin } ],
[qw{ /var /usr/bin } ],
[qw{ /usr/bin /usr/bin } ],
);
for ( @pairs ) {
my ($path, $subpath) = @$_;
my $within = is_within($subpath, $path);
printf qq{"%s" is %swithin "%s" (%s)\n},
$subpath,
($within ? '' : 'not '),
$path,
$within // 'undef';
}
sub is_within {
my ($path, $container) = @_;
my $a2r = abs2rel($path, $container);
return if file_name_is_absolute($a2r) or index($a2r, updir) == 0;
$a2r;
}
output
"/usr/bin" is within "/usr" (bin)
"/usr/bin" is not within "/usr/etc" (undef)
"/usr/bin" is not within "/var" (undef)
"/usr/bin" is within "/usr/bin" (.)