Collapse .. in paths?

J

Jacob Weber

Is there a standard way in Perl to collapse a path that contains .. or
.. directories? For example, say I have a path
"/export/home/bob/../steve/bin/./"
and I'd like to turn it into
"/export/home/steve/bin"

I could do this myself, but I figure the more standard libraries I
use, the fewer bugs I'm likely to have, plus I'd like to keep it
platform-independent.

Barring that, is there a way to tell whether one directory (which may
include .. and .) is a subdirectory of another?

Thanks,
Jacob
(if responding via email, please send to jacob at jacobweber dot com)
 
D

Darren Dunham

Jacob Weber said:
Is there a standard way in Perl to collapse a path that contains .. or
. directories? For example, say I have a path
"/export/home/bob/../steve/bin/./"
and I'd like to turn it into
"/export/home/steve/bin"
I could do this myself, but I figure the more standard libraries I
use, the fewer bugs I'm likely to have, plus I'd like to keep it
platform-independent.

My opinion is that you cannot. If /export/home/bob is a symlink to
/opt/bob, then "/export/home/bob/../steve/bin" and
"/export/home/steve/bin" can be different directories.

My assumption is that you won't find a library that does this because it
couldn't dtermine if it could be done without examining the link itself.

Can I ask what your reason to do this is if you're interested in
platform independence?
 
P

pkent

Is there a standard way in Perl to collapse a path that contains .. or
. directories? For example, say I have a path
"/export/home/bob/../steve/bin/./"
and I'd like to turn it into
"/export/home/steve/bin"

I could do this myself, but I figure the more standard libraries I
use, the fewer bugs I'm likely to have, plus I'd like to keep it
platform-independent.


Look at File::Spec - it offers many platform independent routines for
working with paths, and is bundled with perl.

P
 
M

Martien Verbruggen

Is there a standard way in Perl to collapse a path that contains .. or
. directories? For example, say I have a path
"/export/home/bob/../steve/bin/./"
and I'd like to turn it into
"/export/home/steve/bin"

I could do this myself, but I figure the more standard libraries I
use, the fewer bugs I'm likely to have, plus I'd like to keep it
platform-independent.

Then you can't do it. On a file system that supports symbolic links,
like Unix file systems do, "/export/home/bob/../steve/bin/./" and
"/export/home/steve/bin" are not necessarily the same, if
/export/home/bob is a symbolic link to somewhere else.

Assume /export/home/bob is a symlink to /auto/home/bob, then
/export/home/bob/../steve/./ will refer to /auto/home/steve,
which is not the same as /export/home/steve (unless
/export/home/steve is also a symlink to /auto/home/steve).
Barring that, is there a way to tell whether one directory (which may
include .. and .) is a subdirectory of another?

The Cwd module (standard with Perl) has the realpath() or abs_path()
function, which can give you the canonical pathname of their argument.
If you do that for both, and compare those results, you should be able
to tell.

Martien
 
J

John J. Trammell

Is there a standard way in Perl to collapse a path that contains .. or
. directories? For example, say I have a path
"/export/home/bob/../steve/bin/./"
and I'd like to turn it into
"/export/home/steve/bin"

I don't know about "standard" (maybe something in File::Spec or
File::Spec::Unix?), but I have used:

$foo =~ s{/\./}{/}g;
1 while $foo =~ s{/[^/]+(?<!\.\.)/\.\./}{/};
 
B

Ben Morrow

Then you can't do it. On a file system that supports symbolic links,
like Unix file systems do, "/export/home/bob/../steve/bin/./" and
"/export/home/steve/bin" are not necessarily the same, if
/export/home/bob is a symbolic link to somewhere else.

Assume /export/home/bob is a symlink to /auto/home/bob, then
/export/home/bob/../steve/./ will refer to /auto/home/steve,
which is not the same as /export/home/steve (unless
/export/home/steve is also a symlink to /auto/home/steve).

You are of course right, but this isn't a platform-independence
problem. It is a problem inherent in filesystems with symlinks: there
are simply two ways to resolve the path /export/home/bob/../steve.

The 'logical' way is to /export/home/steve; I don't know of a Perl
module that will do this. You could write a function to do it fairly
simply: (untested)

use File::Spec::Function qw/:ALL/;
use Carp;

sub realpath_log {
my $path = canonpath shift;

# this assumes the path refers to a directory
my ($vol, $dirs) = splitpath $path, 'no file';
my @dirs = splitdir $dirs;

my ($ups, @real);
for (reverse @dirs) {
# this assumes there is only one representation of ..
$_ eq updir and $ups++, next;
$ups and $ups--, next;
push @real, $_;
}

# On unix systems, canonpath will convert /../.. to /. If this
# hasn't been done, it must not hold on this OS, so we have an
# invalid path.
$ups and carp "invalid path: $path", return;

return catpath $vol, catdir @real;
}

The 'physical' way is to /auto/home/steve; you can do that, as you
say, with Cwd::abs_path; provided the file exists. If it doesn't,
Cwd::abs_path will fail. The OP needs to decide which of the two
alternatives he wants.
The Cwd module (standard with Perl) has the realpath() or abs_path()
function, which can give you the canonical pathname of their argument.
If you do that for both, and compare those results, you should be able
to tell.

I would not recommend simply comparing the absolute (or even
canonical) paths: while it will work on Unix, it may well not on other
systems[1]. I have used this routine in the past:

use File::Spec::Functions qw/:ALL/;

# Returns true if $ful is a subdir of $pre.
# This assumes that both paths passed represent directories rather
# than files. On some systems, this makes a difference.
# This will *not* remove /../ components before testing. If you need
# to, change the 'canonpath's into 'abs_path's.

BEGIN { *is_abs = \&File::Spec::Functions::file_name_is_absolute }
# Who came up with that name?? ;)

sub is_prefix {
my ($pre, $ful) = @_;

if (is_abs $pre or is_abs $ful) {
$pre = rel2abs $pre;
$ful = rel2abs $ful;
}

my (@pre, @ful, $d);

# deal with a potential volume component
($pre[0], $d) = splitpath canonpath($pre), 'no file';
push @pre, splitdir $d;

($ful[0], $d, undef) = splitpath canonpath($ful);
push @ful, splitdir $d;

return not grep { case_tolerant() ?
lc $pre[$_] ne lc $ful[$_] :
$pre[$_] ne $ful[$_] } 0..$#pre;
}

[1] VMS with a VMSish path and a Unixish path; or even Win32 with one
path using \ and one path using /.

Ben
 
M

Martien Verbruggen

Hi. Thanks for all of your quick replies. I'm trying to write a program
which accepts filenames from the command line, but only operates on them
if they're within a certain directory.

I didn't mean to imply that this should be done only through parsing the
path string; you're right that it would need to examine the directories.
I'd like to reslve symbolic links, so it looks like Cwd::abs_path does
what I'm looking for. (Thanks Martien.)

Also see Ben Morrow's reply to my post. He's added some good
information, and a complete, and robust solution, which should work
across more platforms than you actually specified.
Just out of curiosity, both Martien and Darren hinted that this solution
wouldn't be platform-independent. Is this true? (Maybe I'm using the
wrong term; I want the Perl script to run on Windows or Unix.)

I was simply not clearly expressing why I was saying that (I won't
speak for Darren). I interpreted the first part of your question (the
one quoted above) in a way that you wanted to do this by simple string
manipulation. Since there are platforms with symlinks, that cannot be
done. If you are happy to simply translate any path string to its
canonical form, then Cwd::abs_path() should be pretty much
cross-platform.

As Ben pointed out, simply comparing the result of two abs_path()
calls may not be sufficient, depending on how "standardised" the
return value of abs_path() is (Ben gives examples). It is probably
better to use Ben's code.

Martien
 
J

Joe Smith

Is there a standard way in Perl to collapse a path that contains .. or
. directories? For example, say I have a path
"/export/home/bob/../steve/bin/./"
and I'd like to turn it into
"/export/home/steve/bin"

Sometimes that can lead to undesirable behavior.
If /export/home/bob is a symlink to /export/home2/bob (because bob is
such as disk hog), then should /export/home/bob/../steve resolve to
/export/home/steve or /export/home2/steve? Simple string parsing
says the former, but file operations will do the latter.
-Joe
 
R

Robin

Just out of curiosity, both Martien and Darren hinted that this solution
wouldn't be platform-independent. Is this true? (Maybe I'm using the
wrong term; I want the Perl script to run on Windows or Unix.)

You'd probably have to include a routine that checks with operating system
it's using before it's going through the script because windows and unix
have diff operating systems.
peace,
-Robin
 
K

Keith Keller

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1


The irony is, it's the smartest thing she's posted to the newsgroup
thus far.

- --keith

- --
(e-mail address removed)-francisco.ca.us
(try just my userid to email me)
AOLSFAQ=http://wombat.san-francisco.ca.us/cgi-bin/fom

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.2.3 (GNU/Linux)

iD8DBQFADfw/hVcNCxZ5ID8RAmWaAKCOrkOqUYW/tBJDg125AlJUkbYyDgCfZaki
cZ47u+c0Y3n2CUBFAVn/Y64=
=GjV9
-----END PGP SIGNATURE-----
 

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

Forum statistics

Threads
474,145
Messages
2,570,826
Members
47,371
Latest member
Brkaa

Latest Threads

Top