Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl-Sensitive Sunglasses
 
PerlMonks  

How to get a true canonical path

by rinceWind (Monsignor)
on Jun 09, 2004 at 14:06 UTC ( [id://362731]=perlquestion: print w/replies, xml ) Need Help??

rinceWind has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on a module which is required to be portable (at least Unix, Windows and VMS). It is making extensive use of File::Spec and friends.

However, I'm looking at code that handles arbitrary paths that the caller has passed in. There is a distinct possibility that the path might contain updir segments (i.e. '..' in Unix). I want to reduce this to a canonical path. I tried File::Spec->canonpath, and was disappointed to find that it does not deal with '..', nore does rel2abs, nor does no_upwards - all of which suggest that they might do the job.

Have I missed something? Or is there another CPAN module which achieves this? I will write one if there isn't.

--
I'm Not Just Another Perl Hacker

Replies are listed 'Best First'.
Re: How to get a true canonical path
by eserte (Deacon) on Jun 09, 2004 at 14:32 UTC
    Try Cwd::abs_path.
      Yeh - that figures out links pretty well..

      use strict; use Cwd 'abs_path'; my $fileinquestion = "/var/mail/"; my $realpathfor = abs_path($fileinquestion); print "$realpathfor\n";


      If you have to be out inside a system operation, the value of
      pwd -P
      is pretty real

      Out of curiosity I tried a .. operation under win2k with AS.. it seems to work..
      #!perl use strict; use Cwd; use Cwd 'abs_path'; my $q = 'C:\\TEMP\\'; chdir $q; my $dirnow = getcwd(); print "Changed dir to C:\\TEMP\n"; print "Cwd thinks I am in $dirnow\n"; chdir "..\\"; print "Changed dir up one level ..\n"; my $newdirnow = getcwd(); print "Cwd now thinks I am in $newdirnow\n";

      so that might do the right thing if a user provides input including \path\..\another\path, so long as backslashes get treated correctly
      Thanks - Look like it will do the trick. I need to check VMS though.

      One thing I have noticed is that the directory must exist - and must actually be a directory. Feeding a file to Cwd::abs_path causes it to carp with "Not a directory".

      --
      I'm Not Just Another Perl Hacker

        Not on my system (perl5.8.0):
        $ perl -MCwd=abs_path -e 'warn abs_path("$ENV{HOME}/../slavenr/.cshrc" +)' /home/slavenr/.cshrc at -e line 1.
Re: How to get a true canonical path
by valdez (Monsignor) on Jun 09, 2004 at 16:01 UTC

    I use URI:

    #!/usr/bin/perl use strict; use warnings; use URI; my $base_uri = URI->new( '/home/httpd/project/' ); while (my $path = <DATA>) { chomp($path); my $absolute = URI->new_abs( $path, $base_uri ); printf "%s + %s: %s\n", $base_uri->canonical, $path, $absolute->cano +nical; } __DATA__ ../another/ subdir/ ../../../etc/very/dangerous ../project/
    update: it seems that I didn't understand the question, sorry.
    update2: However, it seems that URI::file does understand OS differences and does what you want:
    use URI::file; my $base_uri = URI::file->new( URI::file->cwd ); while (my $path = <DATA>) { chomp($path); my $absolute = URI::file->new_abs( $path, $base_uri ); printf "%s + %s: %s\n", $base_uri->canonical, $path, $absolute->file +; } __DATA__ ../another/ subdir/ ../../../some/other/place ../project/

    HTH, Valerio

Re: How to get a true canonical path
by mojotoad (Monsignor) on Jun 09, 2004 at 16:38 UTC
    Why not just leave the '..' portions in the path? Though the result, after having removed them, might be more aesthetically pleasing, it's really up to the OS how to interpret the updir. To "do the right thing" you probably will end up needing to chdir followed by a cwd.

    For a more thorough discussion of what you're up against, check out some of my comments in Absolute pathnames from relative?.

    Matt

Re: How to get a true canonical path
by jepri (Parson) on Jun 09, 2004 at 14:28 UTC
    This is easy to do if you just want to handle ".." in paths. Not too much code is needed - usually just let the shell resolve the path. But if you want to handle symlinks in Unix, then you are in a world of confusion.

    If you want to demand a GNU environment, there is a GLIBC function called something like "cannonical_path" which will do all the work for you. Otherwise... ouch.

    ___________________
    Jeremy
    I didn't believe in evil until I dated it.

Re: How to get a true canonical path
by pelagic (Priest) on Jun 09, 2004 at 21:14 UTC
    I use the following:
    $abs_file = File::Spec->rel2abs( $_, $directory ); $canon_file = elimupdir( $abs_file ); sub elimupdir { my $foo = shift; my( $volume, $directories, $file ) = File::Spec->splitpath(File::S +pec->canonpath( $foo )); my( @dur ) = File::Spec->splitdir( $directories ); my @dar; foreach(@dur){ if( $_ eq $updir ){ pop @dar; } else { push @dar, $_; } } return File::Spec->catpath( $volume, File::Spec->catdir( @dar ), $file ); }

    pelagic
      Sub is nice, here is returns relative path to current directory:
      # sub file_normalize: based on https://www.perlmonks.org/?node_id=3628 +93 sub file_normalize { use File::Spec::Functions; my $dir_ref = "."; my $file = shift; $file = File::Spec->rel2abs($file); my( $volume, $directories, $basename ) = File::Spec->splitpath(File:: +Spec->canonpath($file)); my @dirs_in = File::Spec->splitdir( $directories ); my @dirs_out; for my $dir (@dirs_in) { if ($dir eq "..") { pop @dirs_out; } else { push(@dirs_out, $dir); } } return File::Spec->abs2rel(File::Spec->catpath($volume, File::Spec->c +atdir(@dirs_out), $basename), $dir_ref); }

        Nitpicks: File::Spec::Functions doesn't need to be loaded since it isn't being used, and since File::Spec is already being used, I'd recommend File::Spec->curdir and File::Spec->updir instead of "." and "..".

        But as was already mentioned in this thread, such logical cleanups have potentially major caveats. Going out to the filesystem with Cwd's abs_path and related is generally better.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://362731]
Approved by Happy-the-monk
Front-paged by broquaint
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others avoiding work at the Monastery: (3)
As of 2024-03-29 02:24 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found